Aspects propagating other aspects
This CL adds a new parameter to the `aspect()` API called `requires` guarded by `experimental_required_aspects` flag. Through this parameter we can specify a list of aspects needed by the aspect being defined to be propagated before it to the targets it operates on.
Aspects can only require Starlark defined aspects.
PiperOrigin-RevId: 377473191
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AspectCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/AspectCollection.java
index 0cbc338..d0d622f 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/AspectCollection.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AspectCollection.java
@@ -236,8 +236,9 @@
// Calculate all needed aspects. Already discovered aspects are in key set of deps.
// 1) Start from the end of the path. The aspect only sees other aspects that are
// before it
- // 2) Otherwise, check whether 'aspect' is visible to any already seen aspects. If it is visible
- // to 'depAspect', add the 'aspect' to a list of aspects visible to 'depAspect'.
+ // 2) Otherwise, check whether 'aspect' is visible to or required by any already seen aspects.
+ // If it is visible to 'depAspect' or explicitly required by it, add the 'aspect' to a list of
+ // aspects visible to 'depAspect'.
// At the end of this algorithm, key set of 'deps' contains the original aspect list in reverse
// (since we iterate the original list in reverse).
//
@@ -246,8 +247,11 @@
ImmutableList.copyOf(aspectMap.entrySet()).reverse()) {
for (AspectDescriptor depAspectDescriptor : deps.keySet()) {
Aspect depAspect = aspectMap.get(depAspectDescriptor);
- if (depAspect.getDefinition().getRequiredProvidersForAspects()
- .isSatisfiedBy(aspect.getValue().getDefinition().getAdvertisedProviders())) {
+ if (depAspect
+ .getDefinition()
+ .getRequiredProvidersForAspects()
+ .isSatisfiedBy(aspect.getValue().getDefinition().getAdvertisedProviders())
+ || depAspect.getDefinition().requires(aspect.getValue())) {
deps.get(depAspectDescriptor).add(aspect.getKey());
}
}
@@ -287,12 +291,12 @@
}
/**
- * Detect inconsistent duplicate occurrence of an aspect on the path.
- * There is a previous occurrence of {@code aspect} in {@code seenAspects}.
+ * Detect inconsistent duplicate occurrence of an aspect on the path. There is a previous
+ * occurrence of {@code aspect} in {@code seenAspects}.
*
- * If in between that previous occurrence and the newly discovered occurrence
- * there is an aspect that is visible to {@code aspect}, then the second occurrence
- * is inconsistent - the set of aspects it sees is different from the first one.
+ * <p>If in between that previous occurrence and the newly discovered occurrence there is an
+ * aspect that is visible to or required by {@code aspect}, then the second occurrence is
+ * inconsistent - the set of aspects it sees is different from the first one.
*/
private static void validateDuplicateAspect(Aspect aspect, ArrayList<Aspect> seenAspects)
throws AspectCycleOnPathException {
@@ -303,8 +307,11 @@
return;
}
- if (aspect.getDefinition().getRequiredProvidersForAspects()
- .isSatisfiedBy(seenAspect.getDefinition().getAdvertisedProviders())) {
+ if (aspect
+ .getDefinition()
+ .getRequiredProvidersForAspects()
+ .isSatisfiedBy(seenAspect.getDefinition().getAdvertisedProviders())
+ || aspect.getDefinition().requires(seenAspect)) {
throw new AspectCycleOnPathException(aspect.getDescriptor(), seenAspect.getDescriptor());
}
}
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 04db273..8ce6e4d 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
@@ -37,6 +37,7 @@
import com.google.devtools.build.lib.causes.Cause;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.packages.AdvertisedProviderSet;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.AspectClass;
import com.google.devtools.build.lib.packages.Attribute;
@@ -719,7 +720,8 @@
continue;
}
- if (aspect.getDefinition().propagateAlong(attributeName)) {
+ if (aspect.getDefinition().propagateAlong(attributeName)
+ || aspect.getDescriptor().inheritedPropagateAlong(attributeName)) {
filteredAspectPath.add(aspect);
}
}
@@ -773,12 +775,14 @@
Rule toRule = (Rule) toTarget;
ImmutableList.Builder<Aspect> filteredAspectPath = ImmutableList.builder();
-
+ AdvertisedProviderSet advertisedProviders =
+ toRule.getRuleClassObject().getAdvertisedProviders();
for (Aspect aspect : aspects) {
- if (aspect
- .getDefinition()
- .getRequiredProviders()
- .isSatisfiedBy(toRule.getRuleClassObject().getAdvertisedProviders())) {
+ if (aspect.getDefinition().getRequiredProviders().isSatisfiedBy(advertisedProviders)
+ || aspect
+ .getDescriptor()
+ .getInheritedRequiredProviders()
+ .isSatisfiedBy(advertisedProviders)) {
filteredAspectPath.add(aspect);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
index db1048f..5593d21 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
@@ -256,7 +256,14 @@
if (containsNonNoneKey(arguments, ASPECTS_ARG)) {
Object obj = arguments.get(ASPECTS_ARG);
for (StarlarkAspect aspect : Sequence.cast(obj, StarlarkAspect.class, "aspects")) {
- aspect.attachToAttribute(builder);
+ aspect.attachToAttribute(
+ /** baseAspectName= */
+ null,
+ builder,
+ /** inheritedRequiredProviders= */
+ ImmutableList.of(),
+ /** inheritedAttributeAspects= */
+ ImmutableList.of());
}
}
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 1e1e2ff..65a5f4e 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
@@ -527,6 +527,7 @@
Sequence<?> requiredProvidersArg,
Sequence<?> requiredAspectProvidersArg,
Sequence<?> providesArg,
+ Sequence<?> requiredAspects,
Sequence<?> fragments,
Sequence<?> hostFragments,
Sequence<?> toolchains,
@@ -619,6 +620,7 @@
requiredAspectProvidersArg, "required_aspect_providers"),
StarlarkAttrModule.getStarlarkProviderIdentifiers(providesArg),
requiredParams.build(),
+ ImmutableSet.copyOf(Sequence.cast(requiredAspects, StarlarkAspect.class, "requires")),
ImmutableSet.copyOf(Sequence.cast(fragments, String.class, "fragments")),
HostTransition.INSTANCE,
ImmutableSet.copyOf(Sequence.cast(hostFragments, String.class, "host_fragments")),
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Aspect.java b/src/main/java/com/google/devtools/build/lib/packages/Aspect.java
index 716f6b8..c83d2f6 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Aspect.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Aspect.java
@@ -17,6 +17,7 @@
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
@@ -71,6 +72,36 @@
this.aspectDefinition = Preconditions.checkNotNull(aspectDefinition);
}
+ private Aspect(
+ AspectClass aspectClass,
+ AspectDefinition aspectDefinition,
+ AspectParameters parameters,
+ RequiredProviders inheritedRequiredProviders,
+ ImmutableSet<String> inheritedAttributeAspects) {
+ this.aspectDescriptor =
+ new AspectDescriptor(
+ Preconditions.checkNotNull(aspectClass),
+ Preconditions.checkNotNull(parameters),
+ Preconditions.checkNotNull(inheritedRequiredProviders),
+ inheritedAttributeAspects);
+ this.aspectDefinition = Preconditions.checkNotNull(aspectDefinition);
+ }
+
+ public static Aspect forNative(
+ NativeAspectClass nativeAspectClass,
+ AspectParameters parameters,
+ RequiredProviders inheritedRequiredProviders,
+ ImmutableSet<String> inheritedAttributeAspects) {
+ AspectDefinition definition =
+ definitionCache.getUnchecked(nativeAspectClass).getUnchecked(parameters);
+ return new Aspect(
+ nativeAspectClass,
+ definition,
+ parameters,
+ inheritedRequiredProviders,
+ inheritedAttributeAspects);
+ }
+
public static Aspect forNative(
NativeAspectClass nativeAspectClass, AspectParameters parameters) {
AspectDefinition definition =
@@ -85,8 +116,15 @@
public static Aspect forStarlark(
StarlarkAspectClass starlarkAspectClass,
AspectDefinition aspectDefinition,
- AspectParameters parameters) {
- return new Aspect(starlarkAspectClass, aspectDefinition, parameters);
+ AspectParameters parameters,
+ RequiredProviders inheritedRequiredProviders,
+ ImmutableSet<String> inheritedAttributeAspects) {
+ return new Aspect(
+ starlarkAspectClass,
+ aspectDefinition,
+ parameters,
+ inheritedRequiredProviders,
+ inheritedAttributeAspects);
}
/**
@@ -153,7 +191,9 @@
return forStarlark(
(StarlarkAspectClass) aspectDescriptor.getAspectClass(),
aspectDefinition,
- aspectDescriptor.getParameters());
+ aspectDescriptor.getParameters(),
+ aspectDescriptor.getInheritedRequiredProviders(),
+ aspectDescriptor.getInheritedAttributeAspects());
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AspectClass.java b/src/main/java/com/google/devtools/build/lib/packages/AspectClass.java
index c833f14..bf90746 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AspectClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AspectClass.java
@@ -53,7 +53,8 @@
* {@link AspectClass}
* |
* V
- * {@code AspectDescriptor} <- {@link AspectParameters}
+ * {@code AspectDescriptor} <- {@link AspectParameters}, {@code inheritedRequiredProviders},
+ * \ {@code inheritedAttributeAspects}
* \
* V
* {@link Aspect} <- {@link AspectDefinition} (might require loading Starlark files)
@@ -69,8 +70,9 @@
* label of .bzl file + symbol name.
* <li>{@link AspectParameters} is a (key,value) pair list that can be used to parameterize aspect
* classes
- * <li>{@link AspectDescriptor} is a pair of {@code AspectClass} and {@link AspectParameters}. It
- * uniquely identifies the aspect and can be used in SkyKeys.
+ * <li>{@link AspectDescriptor} is a wrapper for {@code AspectClass}, {@link AspectParameters},
+ * {@code inheritedRequiredProviders} and {@code inheritedAttributeAspects}. It uniquely
+ * identifies the aspect and can be used in SkyKeys.
* <li>{@link AspectDefinition} is a class encapsulating the aspect definition (what attributes
* aspoect has, and along which dependencies does it propagate.
* <li>{@link Aspect} is a fully instantiated instance of an Aspect after it is loaded. Getting an
@@ -82,9 +84,10 @@
* </ul>
*
* {@link AspectDescriptor}, or in general, a tuple of ({@link AspectClass}, {@link
- * AspectParameters}) is an identifier that should be used in SkyKeys or in other contexts that need
- * equality for aspects. See also {@link com.google.devtools.build.lib.skyframe.AspectFunction} for
- * details on Skyframe treatment of Aspects.
+ * AspectParameters}), {@code inheritedRequiredProviders} and {@code inheritedAttributeAspects} is
+ * an identifier that should be used in SkyKeys or in other contexts that need equality for aspects.
+ * See also {@link com.google.devtools.build.lib.skyframe.AspectFunction} for details on Skyframe
+ * treatment of Aspects.
*
* @see com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory
* @see com.google.devtools.build.lib.skyframe.AspectFunction
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 bf5c3e0..1298594 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
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.packages;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -91,6 +92,8 @@
*/
private final BiPredicate<Object, String> propagateViaAttribute;
+ private final ImmutableSet<AspectClass> requiredAspectClasses;
+
public AdvertisedProviderSet getAdvertisedProviders() {
return advertisedProviders;
}
@@ -108,7 +111,8 @@
@Nullable ConfigurationFragmentPolicy configurationFragmentPolicy,
boolean applyToFiles,
boolean applyToGeneratingRules,
- BiPredicate<Object, String> propagateViaAttribute) {
+ BiPredicate<Object, String> propagateViaAttribute,
+ ImmutableSet<AspectClass> requiredAspectClasses) {
this.aspectClass = aspectClass;
this.advertisedProviders = advertisedProviders;
this.requiredProviders = requiredProviders;
@@ -122,6 +126,7 @@
this.applyToFiles = applyToFiles;
this.applyToGeneratingRules = applyToGeneratingRules;
this.propagateViaAttribute = propagateViaAttribute;
+ this.requiredAspectClasses = requiredAspectClasses;
}
public String getName() {
@@ -169,7 +174,7 @@
return requiredProvidersForAspects;
}
- /** Returns the set of required aspects for a given attribute. */
+ /** Returns whether the aspect propagates along the give {@code attributeName} or not. */
public boolean propagateAlong(String attributeName) {
if (restrictToAttributes != null) {
return restrictToAttributes.contains(attributeName);
@@ -177,6 +182,12 @@
return true;
}
+ /** Returns the set of attributes along which the aspect propagates. */
+ @VisibleForTesting
+ public ImmutableSet<String> getRestrictToAttributes() {
+ return restrictToAttributes;
+ }
+
/** Returns the set of configuration fragments required by this Aspect. */
public ConfigurationFragmentPolicy getConfigurationFragmentPolicy() {
return configurationFragmentPolicy;
@@ -212,6 +223,11 @@
return aspect.getDefinition().getRequiredProviders().isSatisfiedBy(advertisedProviderSet);
}
+ /** Checks if the given {@code maybeRequiredAspect} is required by this aspect definition */
+ public boolean requires(Aspect maybeRequiredAspect) {
+ return requiredAspectClasses.contains(maybeRequiredAspect.getAspectClass());
+ }
+
@Nullable
private static Label maybeGetRepositoryRelativeLabel(Rule from, @Nullable Label label) {
return label == null ? null : from.getLabel().resolveRepositoryRelative(label);
@@ -273,6 +289,7 @@
private boolean applyToGeneratingRules = false;
private final List<Label> requiredToolchains = new ArrayList<>();
private boolean useToolchainTransition = false;
+ private ImmutableSet<AspectClass> requiredAspectClasses = ImmutableSet.of();
public Builder(AspectClass aspectClass) {
this.aspectClass = aspectClass;
@@ -323,6 +340,15 @@
}
/**
+ * Asserts that this aspect requires a list of aspects to be applied before it on the configured
+ * target.
+ */
+ public Builder requiredAspectClasses(ImmutableSet<AspectClass> requiredAspectClasses) {
+ this.requiredAspectClasses = requiredAspectClasses;
+ return this;
+ }
+
+ /**
* Optional predicate to conditionally propagate down an attribute based on the {@link
* BuildConfiguration}.
*
@@ -605,7 +631,8 @@
configurationFragmentPolicy.build(),
applyToFiles,
applyToGeneratingRules,
- propagateViaAttribute);
+ propagateViaAttribute,
+ requiredAspectClasses);
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AspectDescriptor.java b/src/main/java/com/google/devtools/build/lib/packages/AspectDescriptor.java
index 947b8d5..aac0539 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AspectDescriptor.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AspectDescriptor.java
@@ -15,13 +15,16 @@
package com.google.devtools.build.lib.packages;
import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.protobuf.TextFormat;
import java.util.Map;
import java.util.Objects;
+import javax.annotation.Nullable;
/**
- * A pair of {@link AspectClass} and {@link AspectParameters}.
+ * A wrapper for {@link AspectClass}, {@link AspectParameters}, {@code inheritedRequiredProviders}
+ * and {@code inheritedAttributeAspects}
*
* <p>Used for dependency resolution.
*/
@@ -30,13 +33,42 @@
private final AspectClass aspectClass;
private final AspectParameters aspectParameters;
- public AspectDescriptor(AspectClass aspectClass, AspectParameters aspectParameters) {
+ /**
+ * Inherited required providers to enable aspects required by other aspects to be propagated along
+ * with their main aspect until they can be applied.
+ */
+ private final RequiredProviders inheritedRequiredProviders;
+ /**
+ * Inherited required providers to enable aspects required by other aspects to be propagated along
+ * with their main aspect based on its propgation attributes.
+ */
+ @Nullable private final ImmutableSet<String> inheritedAttributeAspects;
+
+ public AspectDescriptor(
+ AspectClass aspectClass,
+ AspectParameters aspectParameters,
+ RequiredProviders inheritedRequiredProviders,
+ ImmutableSet<String> inheritedAttributeAspects) {
this.aspectClass = aspectClass;
this.aspectParameters = aspectParameters;
+ this.inheritedRequiredProviders = inheritedRequiredProviders;
+ this.inheritedAttributeAspects = inheritedAttributeAspects;
+ }
+
+ public AspectDescriptor(AspectClass aspectClass, AspectParameters aspectParameters) {
+ this(
+ aspectClass,
+ aspectParameters,
+ /*inheritedRequiredProviders=*/ RequiredProviders.acceptNoneBuilder().build(),
+ /*inheritedAttributeAspects=*/ ImmutableSet.of());
}
public AspectDescriptor(AspectClass aspectClass) {
- this(aspectClass, AspectParameters.EMPTY);
+ this(
+ aspectClass,
+ AspectParameters.EMPTY,
+ /*inheritedRequiredProviders=*/ RequiredProviders.acceptNoneBuilder().build(),
+ /*inheritedAttributeAspects=*/ ImmutableSet.of());
}
public AspectClass getAspectClass() {
@@ -47,9 +79,26 @@
return aspectParameters;
}
+ public RequiredProviders getInheritedRequiredProviders() {
+ return inheritedRequiredProviders;
+ }
+
+ @Nullable
+ public ImmutableSet<String> getInheritedAttributeAspects() {
+ return inheritedAttributeAspects;
+ }
+
+ public boolean inheritedPropagateAlong(String attributeName) {
+ if (inheritedAttributeAspects != null) {
+ return inheritedAttributeAspects.contains(attributeName);
+ }
+ return true;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(aspectClass, aspectParameters);
+ return Objects.hash(
+ aspectClass, aspectParameters, inheritedRequiredProviders, inheritedAttributeAspects);
}
@Override
@@ -64,7 +113,9 @@
AspectDescriptor that = (AspectDescriptor) obj;
return Objects.equals(aspectClass, that.aspectClass)
- && Objects.equals(aspectParameters, that.aspectParameters);
+ && Objects.equals(aspectParameters, that.aspectParameters)
+ && Objects.equals(inheritedRequiredProviders, that.inheritedRequiredProviders)
+ && Objects.equals(inheritedAttributeAspects, that.inheritedAttributeAspects);
}
@Override
@@ -79,26 +130,37 @@
* parseable.
*/
public String getDescription() {
- if (aspectParameters.isEmpty()) {
- return aspectClass.getName();
+ StringBuilder builder = new StringBuilder(aspectClass.getName());
+ if (!aspectParameters.isEmpty()) {
+ builder.append('[');
+ ImmutableMultimap<String, String> attributes = aspectParameters.getAttributes();
+ boolean first = true;
+ for (Map.Entry<String, String> attribute : attributes.entries()) {
+ if (!first) {
+ builder.append(',');
+ } else {
+ first = false;
+ }
+ builder.append(attribute.getKey());
+ builder.append("=\"");
+ builder.append(TextFormat.escapeDoubleQuotesAndBackslashes(attribute.getValue()));
+ builder.append("\"");
+ }
+ builder.append(']');
}
- StringBuilder builder = new StringBuilder(aspectClass.getName());
- builder.append('[');
- ImmutableMultimap<String, String> attributes = aspectParameters.getAttributes();
- boolean first = true;
- for (Map.Entry<String, String> attribute : attributes.entries()) {
- if (!first) {
- builder.append(',');
- } else {
- first = false;
- }
- builder.append(attribute.getKey());
- builder.append("=\"");
- builder.append(TextFormat.escapeDoubleQuotesAndBackslashes(attribute.getValue()));
- builder.append("\"");
+ if (inheritedAttributeAspects == null) {
+ builder.append("[*]");
+ } else if (!inheritedAttributeAspects.isEmpty()) {
+ builder.append(inheritedAttributeAspects);
}
- builder.append(']');
+
+ if (!inheritedRequiredProviders.equals(RequiredProviders.acceptNoneBuilder().build())) {
+ builder.append('[');
+ builder.append(inheritedRequiredProviders);
+ builder.append(']');
+ }
+
return builder.toString();
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
index d7f4a0b..29149fe 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
@@ -78,12 +78,40 @@
/** Wraps the information necessary to construct an Aspect. */
@VisibleForSerialization
abstract static class RuleAspect<C extends AspectClass> {
+ private static final ImmutableList<String> ALL_ATTR_ASPECTS = ImmutableList.of("*");
+
protected final C aspectClass;
protected final Function<Rule, AspectParameters> parametersExtractor;
+ protected String baseAspectName;
+ protected ImmutableList.Builder<ImmutableSet<StarlarkProviderIdentifier>>
+ inheritedRequiredProviders;
+ protected ImmutableList.Builder<String> inheritedAttributeAspects;
+ protected boolean inheritedAllProviders = false;
+ protected boolean inheritedAllAttributes = false;
+
private RuleAspect(C aspectClass, Function<Rule, AspectParameters> parametersExtractor) {
this.aspectClass = aspectClass;
this.parametersExtractor = parametersExtractor;
+ this.inheritedRequiredProviders = ImmutableList.builder();
+ this.inheritedAttributeAspects = ImmutableList.builder();
+ }
+
+ private RuleAspect(
+ C aspectClass,
+ Function<Rule, AspectParameters> parametersExtractor,
+ String baseAspectName,
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders,
+ ImmutableList<String> inheritedAttributeAspects) {
+ this.aspectClass = aspectClass;
+ this.parametersExtractor = parametersExtractor;
+ this.baseAspectName = baseAspectName;
+ this.inheritedRequiredProviders = ImmutableList.builder();
+ this.inheritedAttributeAspects = ImmutableList.builder();
+ if (baseAspectName != null) {
+ updateInheritedRequiredProviders(inheritedRequiredProviders);
+ updateInheritedAttributeAspects(inheritedAttributeAspects);
+ }
}
public String getName() {
@@ -99,6 +127,70 @@
public C getAspectClass() {
return aspectClass;
}
+
+ protected void updateInheritedRequiredProviders(
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> requiredProviders) {
+ if (!inheritedAllProviders && !requiredProviders.isEmpty()) {
+ inheritedRequiredProviders.addAll(requiredProviders);
+ } else {
+ inheritedAllProviders = true;
+ }
+ }
+
+ protected void updateInheritedAttributeAspects(ImmutableList<String> attributeAspects) {
+ if (!inheritedAllAttributes && !ALL_ATTR_ASPECTS.equals(attributeAspects)) {
+ inheritedAttributeAspects.addAll(attributeAspects);
+ } else {
+ inheritedAllAttributes = true;
+ }
+ }
+
+ protected RequiredProviders buildInheritedRequiredProviders() {
+ if (baseAspectName == null) {
+ return RequiredProviders.acceptNoneBuilder().build();
+ } else if (inheritedAllProviders) {
+ return RequiredProviders.acceptAnyBuilder().build();
+ } else {
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProvidersList =
+ inheritedRequiredProviders.build();
+ RequiredProviders.Builder inheritedRequiredProvidersBuilder =
+ RequiredProviders.acceptAnyBuilder();
+ for (ImmutableSet<StarlarkProviderIdentifier> providerSet :
+ inheritedRequiredProvidersList) {
+ if (!providerSet.isEmpty()) {
+ inheritedRequiredProvidersBuilder.addStarlarkSet(providerSet);
+ }
+ }
+ return inheritedRequiredProvidersBuilder.build();
+ }
+ }
+
+ @Nullable
+ protected ImmutableSet<String> buildInheritedAttributeAspects() {
+ if (baseAspectName == null) {
+ return ImmutableSet.of();
+ } else if (inheritedAllAttributes) {
+ return null;
+ } else {
+ return ImmutableSet.<String>copyOf(inheritedAttributeAspects.build());
+ }
+ }
+
+ @VisibleForSerialization
+ public ImmutableList<ImmutableSet<StarlarkProviderIdentifier>>
+ getInheritedRequiredProvidersList() {
+ return inheritedRequiredProviders.build();
+ }
+
+ @VisibleForSerialization
+ public ImmutableList<String> getInheritedAttributeAspectsList() {
+ return inheritedAttributeAspects.build();
+ }
+
+ @VisibleForSerialization
+ public String getBaseAspectName() {
+ return baseAspectName;
+ }
}
private static class NativeRuleAspect extends RuleAspect<NativeAspectClass> {
@@ -107,10 +199,30 @@
super(aspectClass, parametersExtractor);
}
+ NativeRuleAspect(
+ NativeAspectClass aspectClass,
+ Function<Rule, AspectParameters> parametersExtractor,
+ String baseAspectName,
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProvidersList,
+ ImmutableList<String> inheritedAttributeAspectsList) {
+ super(
+ aspectClass,
+ parametersExtractor,
+ baseAspectName,
+ inheritedRequiredProvidersList,
+ inheritedAttributeAspectsList);
+ }
+
@Override
public Aspect getAspect(Rule rule) {
AspectParameters params = parametersExtractor.apply(rule);
- return params == null ? null : Aspect.forNative(aspectClass, params);
+ return params == null
+ ? null
+ : Aspect.forNative(
+ aspectClass,
+ params,
+ buildInheritedRequiredProviders(),
+ buildInheritedAttributeAspects());
}
}
@@ -120,8 +232,17 @@
private final StarlarkDefinedAspect aspect;
@VisibleForSerialization
- StarlarkRuleAspect(StarlarkDefinedAspect aspect) {
- super(aspect.getAspectClass(), aspect.getDefaultParametersExtractor());
+ StarlarkRuleAspect(
+ StarlarkDefinedAspect aspect,
+ String baseAspectName,
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProvidersList,
+ ImmutableList<String> inheritedAttributeAspectsList) {
+ super(
+ aspect.getAspectClass(),
+ aspect.getDefaultParametersExtractor(),
+ baseAspectName,
+ inheritedRequiredProvidersList,
+ inheritedAttributeAspectsList);
this.aspect = aspect;
}
@@ -133,7 +254,12 @@
@Override
public Aspect getAspect(Rule rule) {
AspectParameters parameters = parametersExtractor.apply(rule);
- return Aspect.forStarlark(aspectClass, aspect.getDefinition(parameters), parameters);
+ return Aspect.forStarlark(
+ aspectClass,
+ aspect.getDefinition(parameters),
+ parameters,
+ buildInheritedRequiredProviders(),
+ buildInheritedAttributeAspects());
}
}
@@ -993,6 +1119,9 @@
return this;
}
+ @AutoCodec @AutoCodec.VisibleForSerialization
+ static final Function<Rule, AspectParameters> EMPTY_FUNCTION = input -> AspectParameters.EMPTY;
+
/**
* Asserts that a particular parameterized aspect probably needs to be computed for all direct
* dependencies through this attribute.
@@ -1019,14 +1148,51 @@
return this.aspect(aspect, EMPTY_FUNCTION);
}
- @AutoCodec @AutoCodec.VisibleForSerialization
- static final Function<Rule, AspectParameters> EMPTY_FUNCTION = input -> AspectParameters.EMPTY;
+ public Builder<TYPE> aspect(
+ StarlarkDefinedAspect starlarkAspect,
+ String baseAspectName,
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders,
+ ImmutableList<String> inheritedAttributeAspects)
+ throws EvalException {
+ boolean needsToAdd =
+ checkAndUpdateExistingAspects(
+ starlarkAspect.getName(),
+ baseAspectName,
+ inheritedRequiredProviders,
+ inheritedAttributeAspects);
+ if (needsToAdd) {
+ StarlarkRuleAspect starlarkRuleAspect =
+ new StarlarkRuleAspect(
+ starlarkAspect,
+ baseAspectName,
+ inheritedRequiredProviders,
+ inheritedAttributeAspects);
+ this.aspects.put(starlarkAspect.getName(), starlarkRuleAspect);
+ }
+ return this;
+ }
- public Builder<TYPE> aspect(StarlarkDefinedAspect starlarkAspect) throws EvalException {
- StarlarkRuleAspect starlarkRuleAspect = new StarlarkRuleAspect(starlarkAspect);
- RuleAspect<?> oldAspect = this.aspects.put(starlarkAspect.getName(), starlarkRuleAspect);
- if (oldAspect != null) {
- throw Starlark.errorf("aspect %s added more than once", starlarkAspect.getName());
+ public Builder<TYPE> aspect(
+ NativeAspectClass nativeAspect,
+ String baseAspectName,
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders,
+ ImmutableList<String> inheritedAttributeAspects)
+ throws EvalException {
+ boolean needsToAdd =
+ checkAndUpdateExistingAspects(
+ nativeAspect.getName(),
+ baseAspectName,
+ inheritedRequiredProviders,
+ inheritedAttributeAspects);
+ if (needsToAdd) {
+ NativeRuleAspect nativeRuleAspect =
+ new NativeRuleAspect(
+ nativeAspect,
+ EMPTY_FUNCTION,
+ baseAspectName,
+ inheritedRequiredProviders,
+ inheritedAttributeAspects);
+ this.aspects.put(nativeAspect.getName(), nativeRuleAspect);
}
return this;
}
@@ -1043,6 +1209,40 @@
return this;
}
+ private boolean checkAndUpdateExistingAspects(
+ String aspectName,
+ String baseAspectName,
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders,
+ ImmutableList<String> inheritedAttributeAspects)
+ throws EvalException {
+
+ RuleAspect<?> oldAspect = this.aspects.get(aspectName);
+
+ if (oldAspect != null) {
+ // If the aspect to be added is required by another aspect, i.e. {@code baseAspectName} is
+ // not null, then we need to update its inherited required providers and propgation
+ // attributes.
+ if (baseAspectName != null) {
+ oldAspect.baseAspectName = baseAspectName;
+ oldAspect.updateInheritedRequiredProviders(inheritedRequiredProviders);
+ oldAspect.updateInheritedAttributeAspects(inheritedAttributeAspects);
+ return false; // no need to add the new aspect
+ } else {
+ // If the aspect to be added is not required by another aspect, then we
+ // should throw an error
+ String oldAspectBaseAspectName = oldAspect.baseAspectName;
+ if (oldAspectBaseAspectName != null) {
+ throw Starlark.errorf(
+ "aspect %s was added before as a required aspect of aspect %s",
+ oldAspect.getName(), oldAspectBaseAspectName);
+ }
+ throw Starlark.errorf("aspect %s added more than once", oldAspect.getName());
+ }
+ }
+
+ return true; // we need to add the new aspect
+ }
+
/** Sets the predicate-like edge validity checker. */
public Builder<TYPE> validityPredicate(ValidityPredicate validityPredicate) {
propertyFlags.add(PropertyFlag.STRICT_LABEL_CHECKING);
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkAspect.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkAspect.java
index 7c65d9b..f3efc0a 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkAspect.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.packages;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.starlarkbuildapi.StarlarkAspectApi;
import net.starlark.java.eval.EvalException;
@@ -22,12 +23,30 @@
public interface StarlarkAspect extends StarlarkAspectApi {
/**
- * Attaches this aspect to an attribute.
+ * Attaches this aspect and its required aspects to the given builder.
*
- * @param attrBuilder the builder of the attribute to add this aspect to
+ * <p>Also pass the list of required_providers of the base aspect to its required aspects to
+ * ensure that they will be propgataed to the same targets. But whether the required aspects will
+ * run on these targets or not depends on their required providers.
+ *
+ * <p>The list of attr_aspects of the base aspects is also passed to its required aspects to
+ * ensure that they will be propagated with it along the same attributes.
+ *
+ * @param baseAspectName is the name of the base aspect requiring this aspect, can be {@code null}
+ * if the aspect is directly listed in the attribute aspects list
+ * @param builder is the builder of the attribute to add this aspect to
+ * @param inheritedRequiredProviders is the list of required providers inherited from the aspect
+ * parent aspects
+ * @param inheritedAttributeAspects is the list of attribute aspects inherited from the aspect
+ * parent aspects
* @throws EvalException if this aspect cannot be successfully applied to the given attribute
*/
- void attachToAttribute(Attribute.Builder<?> attrBuilder) throws EvalException;
+ void attachToAttribute(
+ String baseAspectName,
+ Attribute.Builder<?> builder,
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders,
+ ImmutableList<String> inheritedAttributeAspects)
+ throws EvalException;
/** Returns the aspect class for this aspect. */
AspectClass getAspectClass();
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 959d6ec..de01997 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
@@ -23,8 +23,6 @@
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
-import java.util.Arrays;
-import java.util.List;
import java.util.Objects;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Printer;
@@ -41,6 +39,7 @@
private final ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> requiredAspectProviders;
private final ImmutableSet<StarlarkProviderIdentifier> provides;
private final ImmutableSet<String> paramAttributes;
+ private final ImmutableSet<StarlarkAspect> requiredAspects;
private final ImmutableSet<String> fragments;
private final ConfigurationTransition hostTransition;
private final ImmutableSet<String> hostFragments;
@@ -58,6 +57,7 @@
ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> requiredAspectProviders,
ImmutableSet<StarlarkProviderIdentifier> provides,
ImmutableSet<String> paramAttributes,
+ ImmutableSet<StarlarkAspect> requiredAspects,
ImmutableSet<String> fragments,
// The host transition is in lib.analysis, so we can't reference it directly here.
ConfigurationTransition hostTransition,
@@ -72,6 +72,7 @@
this.requiredAspectProviders = requiredAspectProviders;
this.provides = provides;
this.paramAttributes = paramAttributes;
+ this.requiredAspects = requiredAspects;
this.fragments = fragments;
this.hostTransition = hostTransition;
this.hostFragments = hostFragments;
@@ -91,6 +92,7 @@
ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> requiredAspectProviders,
ImmutableSet<StarlarkProviderIdentifier> provides,
ImmutableSet<String> paramAttributes,
+ ImmutableSet<StarlarkAspect> requiredAspects,
ImmutableSet<String> fragments,
// The host transition is in lib.analysis, so we can't reference it directly here.
ConfigurationTransition hostTransition,
@@ -107,6 +109,7 @@
requiredAspectProviders,
provides,
paramAttributes,
+ requiredAspects,
fragments,
hostTransition,
hostFragments,
@@ -160,11 +163,11 @@
this.aspectClass = new StarlarkAspectClass(extensionLabel, name);
}
- private static final List<String> allAttrAspects = Arrays.asList("*");
+ private static final ImmutableList<String> ALL_ATTR_ASPECTS = ImmutableList.of("*");
public AspectDefinition getDefinition(AspectParameters aspectParams) {
AspectDefinition.Builder builder = new AspectDefinition.Builder(aspectClass);
- if (allAttrAspects.equals(attributeAspects)) {
+ if (ALL_ATTR_ASPECTS.equals(attributeAspects)) {
builder.propagateAlongAllAttributes();
} else {
for (String attributeAspect : attributeAspects) {
@@ -201,6 +204,11 @@
builder.addRequiredToolchains(requiredToolchains);
builder.useToolchainTransition(useToolchainTransition);
builder.applyToGeneratingRules(applyToGeneratingRules);
+ ImmutableSet.Builder<AspectClass> requiredAspectsClasses = ImmutableSet.builder();
+ for (StarlarkAspect requiredAspect : requiredAspects) {
+ requiredAspectsClasses.add(requiredAspect.getAspectClass());
+ }
+ builder.requiredAspectClasses(requiredAspectsClasses.build());
return builder.build();
}
@@ -259,12 +267,53 @@
}
@Override
- public void attachToAttribute(Attribute.Builder<?> attrBuilder) throws EvalException {
- if (!isExported()) {
+ public void attachToAttribute(
+ String baseAspectName,
+ Attribute.Builder<?> builder,
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders,
+ ImmutableList<String> inheritedAttributeAspects)
+ throws EvalException {
+ if (!this.isExported()) {
throw Starlark.errorf(
"Aspects should be top-level values in extension files that define them.");
}
- attrBuilder.aspect(this);
+
+ ImmutableList.Builder<ImmutableSet<StarlarkProviderIdentifier>>
+ requiredAspectInheritedRequiredProviders = ImmutableList.builder();
+ ImmutableList.Builder<String> requiredAspectInheritedAttributeAspects = ImmutableList.builder();
+ if (baseAspectName == null) {
+ requiredAspectInheritedRequiredProviders.addAll(this.requiredProviders);
+ requiredAspectInheritedAttributeAspects.addAll(this.attributeAspects);
+ } else {
+ if (!requiredProviders.isEmpty() && !inheritedRequiredProviders.isEmpty()) {
+ requiredAspectInheritedRequiredProviders.addAll(inheritedRequiredProviders);
+ requiredAspectInheritedRequiredProviders.addAll(requiredProviders);
+ }
+ if (!ALL_ATTR_ASPECTS.equals(inheritedAttributeAspects)
+ && !ALL_ATTR_ASPECTS.equals(attributeAspects)) {
+ requiredAspectInheritedAttributeAspects.addAll(inheritedAttributeAspects);
+ requiredAspectInheritedAttributeAspects.addAll(attributeAspects);
+ } else {
+ requiredAspectInheritedAttributeAspects.add("*");
+ }
+ }
+
+ for (StarlarkAspect requiredAspect : requiredAspects) {
+ requiredAspect.attachToAttribute(
+ this.getName(),
+ builder,
+ requiredAspectInheritedRequiredProviders.build(),
+ requiredAspectInheritedAttributeAspects.build());
+ }
+ builder.aspect(this, baseAspectName, inheritedRequiredProviders, inheritedAttributeAspects);
+ }
+
+ public ImmutableSet<StarlarkAspect> getRequiredAspects() {
+ return requiredAspects;
+ }
+
+ public ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> getRequiredProviders() {
+ return requiredProviders;
}
@Override
@@ -283,6 +332,7 @@
&& Objects.equals(requiredAspectProviders, that.requiredAspectProviders)
&& Objects.equals(provides, that.provides)
&& Objects.equals(paramAttributes, that.paramAttributes)
+ && Objects.equals(requiredAspects, that.requiredAspects)
&& Objects.equals(fragments, that.fragments)
&& Objects.equals(hostTransition, that.hostTransition)
&& Objects.equals(hostFragments, that.hostFragments)
@@ -301,6 +351,7 @@
requiredAspectProviders,
provides,
paramAttributes,
+ requiredAspects,
fragments,
hostTransition,
hostFragments,
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeAspect.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeAspect.java
index 6d0fe86..14cf33e 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeAspect.java
@@ -14,7 +14,9 @@
package com.google.devtools.build.lib.packages;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Printer;
/** A natively-defined aspect that is may be referenced by Starlark attribute definitions. */
@@ -25,8 +27,13 @@
}
@Override
- public void attachToAttribute(Attribute.Builder<?> attrBuilder) {
- attrBuilder.aspect(this);
+ public void attachToAttribute(
+ String baseAspectName,
+ Attribute.Builder<?> builder,
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders,
+ ImmutableList<String> inheritedAttributeAspects)
+ throws EvalException {
+ builder.aspect(this, baseAspectName, inheritedRequiredProviders, inheritedAttributeAspects);
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java b/src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java
index d244a60..60262c1 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java
@@ -630,6 +630,17 @@
+ " providers of the aspect.")
public boolean incompatibleTopLevelAspectsRequireProviders;
+ @Option(
+ name = "experimental_required_aspects",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
+ effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
+ metadataTags = {OptionMetadataTag.EXPERIMENTAL},
+ help =
+ "If set to true, allows created aspect to require a list of aspects to be propagated"
+ + " before it.")
+ public boolean experimentalRequiredAspects;
+
/**
* An interner to reduce the number of StarlarkSemantics instances. A single Blaze instance should
* never accumulate a large number of these and being able to shortcut on object identity makes a
@@ -699,6 +710,7 @@
.setBool(
INCOMPATIBLE_TOP_LEVEL_ASPECTS_REQUIRE_PROVIDERS,
incompatibleTopLevelAspectsRequireProviders)
+ .setBool(EXPERIMENTAL_REQUIRED_ASPECTS, experimentalRequiredAspects)
.build();
return INTERNER.intern(semantics);
}
@@ -771,6 +783,7 @@
"-incompatible_visibility_private_attributes_at_definition";
public static final String INCOMPATIBLE_TOP_LEVEL_ASPECTS_REQUIRE_PROVIDERS =
"-incompatible_top_level_aspects_require_providers";
+ public static final String EXPERIMENTAL_REQUIRED_ASPECTS = "-experimental_required_aspects";
// non-booleans
public static final StarlarkSemantics.Key<String> EXPERIMENTAL_BUILTINS_BZL_PATH =
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
index 62b456b..4add5da 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
@@ -219,7 +219,9 @@
Aspect.forStarlark(
starlarkAspect.getAspectClass(),
starlarkAspect.getDefinition(key.getParameters()),
- key.getParameters());
+ key.getParameters(),
+ key.getInheritedRequiredProviders(),
+ key.getInheritedAttributeAspects());
} else {
throw new IllegalStateException();
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java
index 1d73b1c..f6ecb9c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectValueKey.java
@@ -16,6 +16,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Interner;
import com.google.devtools.build.lib.actions.ActionLookupKey;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
@@ -24,6 +25,7 @@
import com.google.devtools.build.lib.packages.AspectClass;
import com.google.devtools.build.lib.packages.AspectDescriptor;
import com.google.devtools.build.lib.packages.AspectParameters;
+import com.google.devtools.build.lib.packages.RequiredProviders;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.skyframe.SkyFunctionName;
import javax.annotation.Nullable;
@@ -158,6 +160,15 @@
return aspectDescriptor.getParameters();
}
+ public RequiredProviders getInheritedRequiredProviders() {
+ return aspectDescriptor.getInheritedRequiredProviders();
+ }
+
+ @Nullable
+ public ImmutableSet<String> getInheritedAttributeAspects() {
+ return aspectDescriptor.getInheritedAttributeAspects();
+ }
+
public AspectDescriptor getAspectDescriptor() {
return aspectDescriptor;
}
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleFunctionsApi.java
index 9a8467f..d7b32f5 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleFunctionsApi.java
@@ -466,6 +466,14 @@
+ "<code>BazInfo</code> *and* <code>QuxInfo</code>."),
@Param(name = "provides", named = true, defaultValue = "[]", doc = PROVIDES_DOC),
@Param(
+ name = "requires",
+ allowedTypes = {@ParamType(type = Sequence.class, generic1 = StarlarkAspectApi.class)},
+ named = true,
+ enableOnlyWithFlag = BuildLanguageOptions.EXPERIMENTAL_REQUIRED_ASPECTS,
+ defaultValue = "[]",
+ valueWhenDisabled = "[]",
+ doc = "(Experimental) List of aspects required to be propagated before this aspect."),
+ @Param(
name = "fragments",
allowedTypes = {@ParamType(type = Sequence.class, generic1 = String.class)},
named = true,
@@ -529,6 +537,7 @@
Sequence<?> requiredProvidersArg,
Sequence<?> requiredAspectProvidersArg,
Sequence<?> providesArg,
+ Sequence<?> requiredAspects,
Sequence<?> fragments,
Sequence<?> hostFragments,
Sequence<?> toolchains,
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkRuleFunctionsApi.java
index 9b163e2..9ad4433 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkRuleFunctionsApi.java
@@ -183,6 +183,7 @@
Sequence<?> requiredProvidersArg,
Sequence<?> requiredAspectProvidersArg,
Sequence<?> providesArg,
+ Sequence<?> requiredAspects,
Sequence<?> fragments,
Sequence<?> hostFragments,
Sequence<?> toolchains,
diff --git a/src/test/java/com/google/devtools/build/lib/packages/AttributeTest.java b/src/test/java/com/google/devtools/build/lib/packages/AttributeTest.java
index 75b4d8e..a4db7e1 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/AttributeTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/AttributeTest.java
@@ -23,7 +23,9 @@
import static org.junit.Assert.assertThrows;
import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.BuildOptionsView;
import com.google.devtools.build.lib.analysis.config.HostTransition;
@@ -319,4 +321,229 @@
.build());
assertThat(e).hasMessageThat().contains("may not contain the same rule classes");
}
+
+ private static final Label FAKE_LABEL = Label.parseAbsoluteUnchecked("//fake/label.bzl");
+
+ private static final StarlarkProviderIdentifier STARLARK_P1 =
+ StarlarkProviderIdentifier.forKey(new StarlarkProvider.Key(FAKE_LABEL, "STARLARK_P1"));
+
+ private static final StarlarkProviderIdentifier STARLARK_P2 =
+ StarlarkProviderIdentifier.forKey(new StarlarkProvider.Key(FAKE_LABEL, "STARLARK_P2"));
+
+ private static final StarlarkProviderIdentifier STARLARK_P3 =
+ StarlarkProviderIdentifier.forKey(new StarlarkProvider.Key(FAKE_LABEL, "STARLARK_P3"));
+
+ private static final StarlarkProviderIdentifier STARLARK_P4 =
+ StarlarkProviderIdentifier.forKey(new StarlarkProvider.Key(FAKE_LABEL, "STARLARK_P4"));
+
+ @Test
+ public void testAttrRequiredAspects_inheritAttrAspects() throws Exception {
+ ImmutableList<String> inheritedAttributeAspects1 = ImmutableList.of("attr1", "attr2");
+ ImmutableList<String> inheritedAttributeAspects2 = ImmutableList.of("attr3", "attr2");
+
+ Attribute attr =
+ attr("x", LABEL)
+ .aspect(
+ TestAspects.SIMPLE_ASPECT,
+ "base_aspect_1",
+ /** inheritedRequiredProviders= */
+ ImmutableList.of(),
+ inheritedAttributeAspects1)
+ .aspect(
+ TestAspects.SIMPLE_ASPECT,
+ "base_aspect_2",
+ /** inheritedRequiredProviders= */
+ ImmutableList.of(),
+ inheritedAttributeAspects2)
+ .allowedFileTypes()
+ .build();
+
+ ImmutableList<Aspect> aspects = attr.getAspects(null);
+ assertThat(aspects).hasSize(1);
+ AspectDescriptor aspectDescriptor = aspects.get(0).getDescriptor();
+ assertThat(aspectDescriptor.getInheritedAttributeAspects())
+ .containsExactly("attr1", "attr2", "attr3");
+ }
+
+ @Test
+ public void testAttrRequiredAspects_inheritRequiredProviders() throws Exception {
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders1 =
+ ImmutableList.of(ImmutableSet.of(STARLARK_P1), ImmutableSet.of(STARLARK_P2, STARLARK_P3));
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders2 =
+ ImmutableList.of(ImmutableSet.of(STARLARK_P4), ImmutableSet.of(STARLARK_P2, STARLARK_P3));
+
+ Attribute attr =
+ attr("x", LABEL)
+ .aspect(
+ TestAspects.SIMPLE_ASPECT,
+ "base_aspect_1",
+ inheritedRequiredProviders1,
+ /** inheritedAttributeAspects= */
+ ImmutableList.of())
+ .aspect(
+ TestAspects.SIMPLE_ASPECT,
+ "base_aspect_2",
+ inheritedRequiredProviders2,
+ /** inheritedAttributeAspects= */
+ ImmutableList.of())
+ .allowedFileTypes()
+ .build();
+
+ ImmutableList<Aspect> aspects = attr.getAspects(null);
+ assertThat(aspects).hasSize(1);
+
+ RequiredProviders actualInheritedRequiredProviders =
+ aspects.get(0).getDescriptor().getInheritedRequiredProviders();
+ AdvertisedProviderSet expectedOkSet1 =
+ AdvertisedProviderSet.builder().addStarlark(STARLARK_P1).build();
+ assertThat(actualInheritedRequiredProviders.isSatisfiedBy(expectedOkSet1)).isTrue();
+
+ AdvertisedProviderSet expectedOkSet2 =
+ AdvertisedProviderSet.builder().addStarlark(STARLARK_P4).build();
+ assertThat(actualInheritedRequiredProviders.isSatisfiedBy(expectedOkSet2)).isTrue();
+
+ AdvertisedProviderSet expectedOkSet3 =
+ AdvertisedProviderSet.builder().addStarlark(STARLARK_P2).addStarlark(STARLARK_P3).build();
+ assertThat(actualInheritedRequiredProviders.isSatisfiedBy(expectedOkSet3)).isTrue();
+
+ assertThat(actualInheritedRequiredProviders.isSatisfiedBy(AdvertisedProviderSet.ANY)).isTrue();
+ assertThat(actualInheritedRequiredProviders.isSatisfiedBy(AdvertisedProviderSet.EMPTY))
+ .isFalse();
+ }
+
+ @Test
+ public void testAttrRequiredAspects_aspectAlreadyExists_inheritAttrAspects() throws Exception {
+ ImmutableList<String> inheritedAttributeAspects = ImmutableList.of("attr1", "attr2");
+
+ Attribute attr =
+ attr("x", LABEL)
+ .aspect(TestAspects.SIMPLE_ASPECT)
+ .aspect(
+ TestAspects.SIMPLE_ASPECT,
+ "base_aspect",
+ /** inheritedRequiredProviders = */
+ ImmutableList.of(),
+ inheritedAttributeAspects)
+ .allowedFileTypes()
+ .build();
+
+ ImmutableList<Aspect> aspects = attr.getAspects(null);
+ assertThat(aspects).hasSize(1);
+ AspectDescriptor aspectDescriptor = aspects.get(0).getDescriptor();
+ assertThat(aspectDescriptor.getInheritedAttributeAspects()).containsExactly("attr1", "attr2");
+ }
+
+ @Test
+ public void testAttrRequiredAspects_aspectAlreadyExists_inheritRequiredProviders()
+ throws Exception {
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders =
+ ImmutableList.of(ImmutableSet.of(STARLARK_P1), ImmutableSet.of(STARLARK_P2, STARLARK_P3));
+
+ Attribute attr =
+ attr("x", LABEL)
+ .aspect(TestAspects.SIMPLE_ASPECT)
+ .aspect(
+ TestAspects.SIMPLE_ASPECT,
+ "base_aspect",
+ inheritedRequiredProviders,
+ /** inheritedAttributeAspects= */
+ ImmutableList.of())
+ .allowedFileTypes()
+ .build();
+
+ ImmutableList<Aspect> aspects = attr.getAspects(null);
+ assertThat(aspects).hasSize(1);
+
+ RequiredProviders actualInheritedRequiredProviders =
+ aspects.get(0).getDescriptor().getInheritedRequiredProviders();
+ AdvertisedProviderSet expectedOkSet1 =
+ AdvertisedProviderSet.builder().addStarlark(STARLARK_P1).build();
+ assertThat(actualInheritedRequiredProviders.isSatisfiedBy(expectedOkSet1)).isTrue();
+
+ AdvertisedProviderSet expectedOkSet2 =
+ AdvertisedProviderSet.builder().addStarlark(STARLARK_P4).build();
+ assertThat(actualInheritedRequiredProviders.isSatisfiedBy(expectedOkSet2)).isFalse();
+
+ AdvertisedProviderSet expectedOkSet3 =
+ AdvertisedProviderSet.builder().addStarlark(STARLARK_P2).addStarlark(STARLARK_P3).build();
+ assertThat(actualInheritedRequiredProviders.isSatisfiedBy(expectedOkSet3)).isTrue();
+
+ assertThat(actualInheritedRequiredProviders.isSatisfiedBy(AdvertisedProviderSet.ANY)).isTrue();
+ assertThat(actualInheritedRequiredProviders.isSatisfiedBy(AdvertisedProviderSet.EMPTY))
+ .isFalse();
+ }
+
+ @Test
+ public void testAttrRequiredAspects_inheritAllAttrAspects() throws Exception {
+ ImmutableList<String> inheritedAttributeAspects1 = ImmutableList.of("attr1", "attr2");
+ ImmutableList<String> inheritedAttributeAspects2 = ImmutableList.of("*");
+
+ Attribute attr =
+ attr("x", LABEL)
+ .aspect(TestAspects.SIMPLE_ASPECT)
+ .aspect(
+ TestAspects.SIMPLE_ASPECT,
+ "base_aspect_1",
+ /** inheritedRequiredProviders = */
+ ImmutableList.of(),
+ inheritedAttributeAspects1)
+ .aspect(
+ TestAspects.SIMPLE_ASPECT,
+ "base_aspect_2",
+ /** inheritedRequiredProviders = */
+ ImmutableList.of(),
+ inheritedAttributeAspects2)
+ .allowedFileTypes()
+ .build();
+
+ ImmutableList<Aspect> aspects = attr.getAspects(null);
+ assertThat(aspects).hasSize(1);
+ AspectDescriptor aspectDescriptor = aspects.get(0).getDescriptor();
+ assertThat(aspectDescriptor.getInheritedAttributeAspects()).isNull();
+ }
+
+ @Test
+ public void testAttrRequiredAspects_inheritAllRequiredProviders() throws Exception {
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders1 =
+ ImmutableList.of();
+ ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders2 =
+ ImmutableList.of(ImmutableSet.of(STARLARK_P4), ImmutableSet.of(STARLARK_P2, STARLARK_P3));
+
+ Attribute attr =
+ attr("x", LABEL)
+ .aspect(TestAspects.SIMPLE_ASPECT)
+ .aspect(
+ TestAspects.SIMPLE_ASPECT,
+ "base_aspect_1",
+ inheritedRequiredProviders1,
+ /** inheritedAttributeAspects= */
+ ImmutableList.of())
+ .aspect(
+ TestAspects.SIMPLE_ASPECT,
+ "base_aspect_2",
+ inheritedRequiredProviders2,
+ /** inheritedAttributeAspects= */
+ ImmutableList.of())
+ .allowedFileTypes()
+ .build();
+
+ ImmutableList<Aspect> aspects = attr.getAspects(null);
+ assertThat(aspects).hasSize(1);
+ AspectDescriptor aspectDescriptor = aspects.get(0).getDescriptor();
+ assertThat(aspectDescriptor.getInheritedRequiredProviders())
+ .isEqualTo(RequiredProviders.acceptAnyBuilder().build());
+ }
+
+ @Test
+ public void testAttrRequiredAspects_defaultInheritedRequiredProvidersAndAttrAspects()
+ throws Exception {
+ Attribute attr = attr("x", LABEL).aspect(TestAspects.SIMPLE_ASPECT).allowedFileTypes().build();
+
+ ImmutableList<Aspect> aspects = attr.getAspects(null);
+ assertThat(aspects).hasSize(1);
+ AspectDescriptor aspectDescriptor = aspects.get(0).getDescriptor();
+ assertThat(aspectDescriptor.getInheritedAttributeAspects()).isEmpty();
+ assertThat(aspectDescriptor.getInheritedRequiredProviders())
+ .isEqualTo(RequiredProviders.acceptNoneBuilder().build());
+ }
}
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 326e56d..b815308 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
@@ -2298,6 +2298,82 @@
+ "(when propagating to //test:r1)");
}
+ @Test
+ public void aspectRequiresAspectInconsistentVisibility() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target,ctx):",
+ " return struct()",
+ "a1 = aspect(_aspect_impl, attr_aspects = ['dep'])",
+ "a2 = aspect(_aspect_impl, attr_aspects = ['dep'], requires = [a1])",
+ "def _rule_impl(ctx):",
+ " pass",
+ "r1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2])})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r1', 'r2')",
+ "r1(name = 'r0')",
+ "r1(name = 'r1', dep = ':r0')",
+ "r2(name = 'r2', dep = ':r1')",
+ "r1(name = 'r1_1', dep = ':r2')",
+ "r2(name = 'r2_1', dep = ':r1_1')");
+ reporter.removeHandler(failFastHandler);
+ useConfiguration("--experimental_required_aspects");
+
+ // The call to `update` does not throw an exception when "--keep_going" is passed in the
+ // WithKeepGoing test suite. Otherwise, it throws ViewCreationFailedException.
+ if (keepGoing()) {
+ AnalysisResult result = update("//test:r2_1");
+ assertThat(result.hasError()).isTrue();
+ } else {
+ assertThrows(ViewCreationFailedException.class, () -> update("//test:r2_1"));
+ }
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:3:3: Aspect //test:aspect.bzl%a2 is"
+ + " applied twice, both before and after aspect //test:aspect.bzl%a1 "
+ + "(when propagating to //test:r1)");
+ }
+
+ @Test
+ public void aspectRequiresAspectInconsistentVisibilityIndirect() throws Exception {
+ scratch.file(
+ "test/aspect.bzl",
+ "def _aspect_impl(target,ctx):",
+ " return struct()",
+ "a1 = aspect(_aspect_impl, attr_aspects = ['dep'])",
+ "a2 = aspect(_aspect_impl, attr_aspects = ['dep'], requires = [a1])",
+ "def _rule_impl(ctx):",
+ " pass",
+ "r1 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a1])})",
+ "r2 = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [a2])})",
+ "r0 = rule(_rule_impl, attrs = { 'dep' : attr.label()})");
+ scratch.file(
+ "test/BUILD",
+ "load(':aspect.bzl', 'r0', 'r1', 'r2')",
+ "r0(name = 'r0')",
+ "r1(name = 'r1', dep = ':r0')",
+ "r2(name = 'r2', dep = ':r1')",
+ "r1(name = 'r1_1', dep = ':r2')",
+ "r2(name = 'r2_1', dep = ':r1_1')",
+ "r0(name = 'r0_1', dep = ':r2_1')");
+ reporter.removeHandler(failFastHandler);
+ useConfiguration("--experimental_required_aspects");
+
+ // The call to `update` does not throw an exception when "--keep_going" is passed in the
+ // WithKeepGoing test suite. Otherwise, it throws ViewCreationFailedException.
+ if (keepGoing()) {
+ AnalysisResult result = update("//test:r0_1");
+ assertThat(result.hasError()).isTrue();
+ } else {
+ assertThrows(ViewCreationFailedException.class, () -> update("//test:r0_1"));
+ }
+ assertContainsEvent(
+ "ERROR /workspace/test/BUILD:3:3: Aspect //test:aspect.bzl%a2 is"
+ + " applied twice, both before and after aspect //test:aspect.bzl%a1 "
+ + "(when propagating to //test:r1)");
+ }
+
/**
* Aspect a3 sees aspect a2, aspect a2 sees aspect a1, but a3 does not see a1. All three aspects
* should still propagate together.
@@ -3420,6 +3496,704 @@
assertThat(Starlark.toIterable(ruleDeps)).containsExactlyElementsIn(expected);
}
+ @Test
+ public void testAspectRequiresAspect_aspectsParameters() throws Exception {
+ scratch.file(
+ "test/defs.bzl",
+ "RequiredAspectProv = provider()",
+ "BaseAspectProv = provider()",
+ "",
+ "def _required_aspect_impl(target, ctx):",
+ " p1_val = 'In required_aspect, p1 = {} on target {}'.format(ctx.attr.p1, target.label)",
+ " p2_val = 'invalid value'",
+ " if not hasattr(ctx.attr, 'p2'):",
+ " p2_val = 'In required_aspect, p2 not found on target {}'.format(target.label)",
+ " return [RequiredAspectProv(p1_val = p1_val, p2_val = p2_val)]",
+ "required_aspect = aspect(",
+ " implementation = _required_aspect_impl,",
+ " attr_aspects = ['dep'],",
+ " attrs = {'p1' : attr.string(values = ['p1_v1', 'p1_v2'])}",
+ ")",
+ "",
+ "def _base_aspect_impl(target, ctx):",
+ " p2_val = 'In base_aspect, p2 = {} on target {}'.format(ctx.attr.p2, target.label)",
+ " p1_val = 'invalid value'",
+ " if not hasattr(ctx.attr, 'p1'):",
+ " p1_val = 'In base_aspect, p1 not found on target {}'.format(target.label)",
+ " return [BaseAspectProv(p1_val = p1_val, p2_val = p2_val)]",
+ "base_aspect = aspect(",
+ " implementation = _base_aspect_impl,",
+ " attr_aspects = ['dep'],",
+ " attrs = {'p2' : attr.string(values = ['p2_v1', 'p2_v2'])},",
+ " requires = [required_aspect],",
+ ")",
+ "",
+ "def _main_rule_impl(ctx):",
+ " return [ctx.attr.dep[RequiredAspectProv], ctx.attr.dep[BaseAspectProv]]",
+ "def _dep_rule_impl(ctx):",
+ " pass",
+ "",
+ "main_rule = rule(",
+ " implementation = _main_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(aspects=[base_aspect]),",
+ " 'p1' : attr.string(values = ['p1_v1', 'p1_v2']),",
+ " 'p2' : attr.string(values = ['p2_v1', 'p2_v2'])",
+ " },",
+ ")",
+ "",
+ "dep_rule = rule(",
+ " implementation = _dep_rule_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
+ "main_rule(",
+ " name = 'main',",
+ " dep = ':dep_target',",
+ " p1 = 'p1_v1',",
+ " p2 = 'p2_v1'",
+ ")",
+ "dep_rule(",
+ " name = 'dep_target',",
+ ")");
+
+ useConfiguration("--experimental_required_aspects");
+ AnalysisResult analysisResult = update("//test:main");
+
+ // Both base_aspect and required_aspect can get their parameters values from the base rule
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StarlarkProvider.Key requiredAspectProv =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "RequiredAspectProv");
+ StructImpl requiredAspectProvider = (StructImpl) configuredTarget.get(requiredAspectProv);
+ assertThat(requiredAspectProvider.getValue("p1_val"))
+ .isEqualTo("In required_aspect, p1 = p1_v1 on target //test:dep_target");
+ assertThat(requiredAspectProvider.getValue("p2_val"))
+ .isEqualTo("In required_aspect, p2 not found on target //test:dep_target");
+
+ StarlarkProvider.Key baseAspectProv =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "BaseAspectProv");
+ StructImpl baseAspectProvider = (StructImpl) configuredTarget.get(baseAspectProv);
+ assertThat(baseAspectProvider.getValue("p1_val"))
+ .isEqualTo("In base_aspect, p1 not found on target //test:dep_target");
+ assertThat(baseAspectProvider.getValue("p2_val"))
+ .isEqualTo("In base_aspect, p2 = p2_v1 on target //test:dep_target");
+ }
+
+ @Test
+ public void testAspectRequiresAspect_ruleAttributes() throws Exception {
+ scratch.file(
+ "test/defs.bzl",
+ "RequiredAspectProv = provider()",
+ "BaseAspectProv = provider()",
+ "",
+ "def _required_aspect_impl(target, ctx):",
+ " p_val = 'In required_aspect, p = {} on target {}'.format(ctx.rule.attr.p, target.label)",
+ " return [RequiredAspectProv(p_val = p_val)]",
+ "required_aspect = aspect(",
+ " implementation = _required_aspect_impl,",
+ " attr_aspects = ['dep'],",
+ ")",
+ "",
+ "def _base_aspect_impl(target, ctx):",
+ " p_val = 'In base_aspect, p = {} on target {}'.format(ctx.rule.attr.p, target.label)",
+ " return [BaseAspectProv(p_val = p_val)]",
+ "base_aspect = aspect(",
+ " implementation = _base_aspect_impl,",
+ " attr_aspects = ['dep'],",
+ " requires = [required_aspect],",
+ ")",
+ "",
+ "def _main_rule_impl(ctx):",
+ " return [ctx.attr.dep[RequiredAspectProv], ctx.attr.dep[BaseAspectProv]]",
+ "def _dep_rule_impl(ctx):",
+ " pass",
+ "",
+ "main_rule = rule(",
+ " implementation = _main_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(aspects=[base_aspect]),",
+ " },",
+ ")",
+ "",
+ "dep_rule = rule(",
+ " implementation = _dep_rule_impl,",
+ " attrs = {",
+ " 'p' : attr.string(values = ['p_v1', 'p_v2']),",
+ " },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
+ "main_rule(",
+ " name = 'main',",
+ " dep = ':dep_target',",
+ ")",
+ "dep_rule(",
+ " name = 'dep_target',",
+ " p = 'p_v2',",
+ ")");
+
+ useConfiguration("--experimental_required_aspects");
+ AnalysisResult analysisResult = update("//test:main");
+
+ // Both base_aspect and required_aspect can see the attributes of the rule they run on
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StarlarkProvider.Key requiredAspectProv =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "RequiredAspectProv");
+ StructImpl requiredAspectProvider = (StructImpl) configuredTarget.get(requiredAspectProv);
+ assertThat(requiredAspectProvider.getValue("p_val"))
+ .isEqualTo("In required_aspect, p = p_v2 on target //test:dep_target");
+
+ StarlarkProvider.Key baseAspectProv =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "BaseAspectProv");
+ StructImpl baseAspectProvider = (StructImpl) configuredTarget.get(baseAspectProv);
+ assertThat(baseAspectProvider.getValue("p_val"))
+ .isEqualTo("In base_aspect, p = p_v2 on target //test:dep_target");
+ }
+
+ @Test
+ public void testAspectRequiresAspect_inheritPropagationAttributes() throws Exception {
+ // base_aspect propagates over base_dep attribute and requires first_required_aspect which
+ // propagates
+ // over first_dep attribute and requires second_required aspect which propagates over second_dep
+ // attribute
+ scratch.file(
+ "test/defs.bzl",
+ "BaseAspectProv = provider()",
+ "FirstRequiredAspectProv = provider()",
+ "SecondRequiredAspectProv = provider()",
+ "",
+ "def _second_required_aspect_impl(target, ctx):",
+ " result = []",
+ " if getattr(ctx.rule.attr, 'second_dep'):",
+ " result += getattr(ctx.rule.attr, 'second_dep')[SecondRequiredAspectProv].result",
+ " result += ['second_required_aspect run on target {}'.format(target.label)]",
+ " return [SecondRequiredAspectProv(result = result)]",
+ "second_required_aspect = aspect(",
+ " implementation = _second_required_aspect_impl,",
+ " attr_aspects = ['second_dep'],",
+ ")",
+ "",
+ "def _first_required_aspect_impl(target, ctx):",
+ " result = []",
+ " result += target[SecondRequiredAspectProv].result",
+ " if getattr(ctx.rule.attr, 'first_dep'):",
+ " result += getattr(ctx.rule.attr, 'first_dep')[FirstRequiredAspectProv].result",
+ " result += ['first_required_aspect run on target {}'.format(target.label)]",
+ " return [FirstRequiredAspectProv(result = result)]",
+ "first_required_aspect = aspect(",
+ " implementation = _first_required_aspect_impl,",
+ " attr_aspects = ['first_dep'],",
+ " requires = [second_required_aspect],",
+ ")",
+ "",
+ "def _base_aspect_impl(target, ctx):",
+ " result = []",
+ " result += target[FirstRequiredAspectProv].result",
+ " if getattr(ctx.rule.attr, 'base_dep'):",
+ " result += getattr(ctx.rule.attr, 'base_dep')[BaseAspectProv].result",
+ " result += ['base_aspect run on target {}'.format(target.label)]",
+ " return [BaseAspectProv(result = result)]",
+ "base_aspect = aspect(",
+ " implementation = _base_aspect_impl,",
+ " attr_aspects = ['base_dep'],",
+ " requires = [first_required_aspect],",
+ ")",
+ "",
+ "def _main_rule_impl(ctx):",
+ " return [ctx.attr.dep[BaseAspectProv]]",
+ "def _dep_rule_impl(ctx):",
+ " pass",
+ "",
+ "main_rule = rule(",
+ " implementation = _main_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(aspects=[base_aspect]),",
+ " },",
+ ")",
+ "",
+ "dep_rule = rule(",
+ " implementation = _dep_rule_impl,",
+ " attrs = {",
+ " 'base_dep': attr.label(),",
+ " 'first_dep': attr.label(),",
+ " 'second_dep': attr.label()",
+ " },",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
+ "main_rule(",
+ " name = 'main',",
+ " dep = ':dep_target',",
+ ")",
+ "dep_rule(",
+ " name = 'dep_target',",
+ " base_dep = ':base_dep_target',",
+ " first_dep = ':first_dep_target',",
+ " second_dep = ':second_dep_target',",
+ ")",
+ "dep_rule(",
+ " name = 'base_dep_target',",
+ ")",
+ "dep_rule(",
+ " name = 'first_dep_target',",
+ ")",
+ "dep_rule(",
+ " name = 'second_dep_target',",
+ ")");
+
+ useConfiguration("--experimental_required_aspects");
+ AnalysisResult analysisResult = update("//test:main");
+
+ // base_aspect should propagate only along its attr_aspects: 'base_dep'
+ // first_required_aspect should propagate along 'base_dep' and 'first_dep'
+ // second_required_aspect should propagate along 'base_dep', 'first_dep' and `second_dep`
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StarlarkProvider.Key baseAspectProv =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "BaseAspectProv");
+ StructImpl baseAspectProvider = (StructImpl) configuredTarget.get(baseAspectProv);
+ assertThat((Sequence<?>) baseAspectProvider.getValue("result"))
+ .containsExactly(
+ "second_required_aspect run on target //test:second_dep_target",
+ "second_required_aspect run on target //test:dep_target",
+ "second_required_aspect run on target //test:first_dep_target",
+ "first_required_aspect run on target //test:first_dep_target",
+ "first_required_aspect run on target //test:dep_target",
+ "second_required_aspect run on target //test:base_dep_target",
+ "first_required_aspect run on target //test:base_dep_target",
+ "base_aspect run on target //test:base_dep_target",
+ "base_aspect run on target //test:dep_target");
+ }
+
+ @Test
+ public void testAspectRequiresAspect_inheritRequiredProviders() throws Exception {
+ // aspect_a requires provider Prov_A and requires aspect_b which requires
+ // provider Prov_B and requires aspect_c which requires provider Prov_C
+ scratch.file(
+ "test/defs.bzl",
+ "Prov_A = provider()",
+ "Prov_B = provider()",
+ "Prov_C = provider()",
+ "",
+ "CollectorProv = provider()",
+ "",
+ "def _aspect_c_impl(target, ctx):",
+ " collector_result = ['aspect_c run on target {} and value of Prov_C ="
+ + " {}'.format(target.label, target[Prov_C].val)]",
+ " return [CollectorProv(result = collector_result)]",
+ "aspect_c = aspect(",
+ " implementation = _aspect_c_impl,",
+ " required_providers = [Prov_C],",
+ ")",
+ "",
+ "def _aspect_b_impl(target, ctx):",
+ " collector_result = []",
+ " collector_result += ctx.rule.attr.dep[CollectorProv].result",
+ " collector_result += ['aspect_b run on target {} and value of Prov_B ="
+ + " {}'.format(target.label, target[Prov_B].val)]",
+ " return [ CollectorProv(result = collector_result)]",
+ "aspect_b = aspect(",
+ " implementation = _aspect_b_impl,",
+ " required_providers = [Prov_B],",
+ " requires = [aspect_c],",
+ ")",
+ "",
+ "def _aspect_a_impl(target, ctx):",
+ " collector_result = []",
+ " collector_result += ctx.rule.attr.dep[CollectorProv].result",
+ " collector_result += ['aspect_a run on target {} and value of Prov_A ="
+ + " {}'.format(target.label, target[Prov_A].val)]",
+ " return [CollectorProv(result = collector_result)]",
+ "aspect_a = aspect(",
+ " implementation = _aspect_a_impl,",
+ " attr_aspects = ['dep'],",
+ " required_providers = [Prov_A],",
+ " requires = [aspect_b],",
+ ")",
+ "",
+ "def _main_rule_impl(ctx):",
+ " return [ctx.attr.dep[CollectorProv]]",
+ "main_rule = rule(",
+ " implementation = _main_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(aspects = [aspect_a]),",
+ " },",
+ ")",
+ "",
+ "def _rule_with_prov_a_impl(ctx):",
+ " return [Prov_A(val='val_a')]",
+ "rule_with_prov_a = rule(",
+ " implementation = _rule_with_prov_a_impl,",
+ " attrs = {",
+ " 'dep': attr.label(),",
+ " },",
+ " provides = [Prov_A]",
+ ")",
+ "",
+ "def _rule_with_prov_b_impl(ctx):",
+ " return [Prov_B(val = 'val_b')]",
+ "rule_with_prov_b = rule(",
+ " implementation = _rule_with_prov_b_impl,",
+ " attrs = {",
+ " 'dep': attr.label(),",
+ " },",
+ " provides = [Prov_B]",
+ ")",
+ "",
+ "def _rule_with_prov_c_impl(ctx):",
+ " return [Prov_C(val = 'val_c')]",
+ "rule_with_prov_c = rule(",
+ " implementation = _rule_with_prov_c_impl,",
+ " provides = [Prov_C]",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'main_rule', 'rule_with_prov_a', 'rule_with_prov_b',"
+ + " 'rule_with_prov_c')",
+ "main_rule(",
+ " name = 'main',",
+ " dep = ':target_with_prov_a',",
+ ")",
+ "rule_with_prov_a(",
+ " name = 'target_with_prov_a',",
+ " dep = ':target_with_prov_b'",
+ ")",
+ "rule_with_prov_b(",
+ " name = 'target_with_prov_b',",
+ " dep = ':target_with_prov_c'",
+ ")",
+ "rule_with_prov_c(",
+ " name = 'target_with_prov_c'",
+ ")");
+
+ useConfiguration("--experimental_required_aspects");
+ AnalysisResult analysisResult = update("//test:main");
+
+ // aspect_a should only run on target_with_prov_a, aspect_b should only run on
+ // target_with_prov_b and aspect_c
+ // should only run on target_with_prov_c.
+ // aspect_c will reach target target_with_prov_c because it inherits the required_providers of
+ // aspect_c otherwise it
+ // would have stopped propagating after target_with_prov_b
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StarlarkProvider.Key collectorProv =
+ new StarlarkProvider.Key(
+ Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "CollectorProv");
+ StructImpl collectorProvider = (StructImpl) configuredTarget.get(collectorProv);
+ assertThat((Sequence<?>) collectorProvider.getValue("result"))
+ .containsExactly(
+ "aspect_c run on target //test:target_with_prov_c and value of Prov_C = val_c",
+ "aspect_b run on target //test:target_with_prov_b and value of Prov_B = val_b",
+ "aspect_a run on target //test:target_with_prov_a and value of Prov_A = val_a")
+ .inOrder();
+ }
+
+ @Test
+ public void testAspectRequiresAspect_inspectRequiredAspectActions() throws Exception {
+ scratch.file(
+ "test/defs.bzl",
+ "def _required_aspect_impl(target, ctx):",
+ " f = ctx.actions.declare_file('dummy.txt')",
+ " ctx.actions.run_shell(outputs = [f], command='echo xxx > $(location f)',",
+ " mnemonic='RequiredAspectAction')",
+ " return struct()",
+ "required_aspect = aspect(",
+ " implementation = _required_aspect_impl,",
+ ")",
+ "",
+ "def _base_aspect_impl(target, ctx):",
+ " required_aspect_action = None",
+ " for action in target.actions:",
+ " if action.mnemonic == 'RequiredAspectAction':",
+ " required_aspect_action = action",
+ " if required_aspect_action:",
+ " return struct(result = 'base_aspect can see required_aspect action')",
+ " else:",
+ " return struct(result = 'base_aspect cannot see required_aspect action')",
+ "base_aspect = aspect(",
+ " implementation = _base_aspect_impl,",
+ " attr_aspects = ['dep'],",
+ " requires = [required_aspect]",
+ ")",
+ "",
+ "def _main_rule_impl(ctx):",
+ " return struct(result = ctx.attr.dep.result)",
+ "main_rule = rule(",
+ " implementation = _main_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(aspects = [base_aspect]),",
+ " },",
+ ")",
+ "",
+ "def _dep_rule_impl(ctx):",
+ " pass",
+ "dep_rule = rule(",
+ " implementation = _dep_rule_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
+ "main_rule(",
+ " name = 'main',",
+ " dep = ':dep_target',",
+ ")",
+ "dep_rule(",
+ " name = 'dep_target',",
+ ")");
+
+ useConfiguration("--experimental_required_aspects");
+ AnalysisResult analysisResult = update("//test:main");
+
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ String result = (String) configuredTarget.get("result");
+ assertThat(result).isEqualTo("base_aspect can see required_aspect action");
+ }
+
+ @Test
+ public void testAspectRequiresAspect_inspectRequiredAspectGeneratedFiles() throws Exception {
+ scratch.file(
+ "test/defs.bzl",
+ "def _required_aspect_impl(target, ctx):",
+ " file = ctx.actions.declare_file('required_aspect_file')",
+ " ctx.actions.write(file, 'data')",
+ " return [OutputGroupInfo(out = [file])]",
+ "required_aspect = aspect(",
+ " implementation = _required_aspect_impl,",
+ ")",
+ "",
+ "def _base_aspect_impl(target, ctx):",
+ " files = ['base_aspect can see file ' + f.path.split('/')[-1] for f in"
+ + " target[OutputGroupInfo].out.to_list()]",
+ " return struct(my_files = files)",
+ "base_aspect = aspect(",
+ " implementation = _base_aspect_impl,",
+ " attr_aspects = ['dep'],",
+ " requires = [required_aspect]",
+ ")",
+ "",
+ "def _main_rule_impl(ctx):",
+ " return struct(my_files = ctx.attr.dep.my_files)",
+ "main_rule = rule(",
+ " implementation = _main_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(aspects = [base_aspect]),",
+ " },",
+ ")",
+ "",
+ "def _dep_rule_impl(ctx):",
+ " pass",
+ "dep_rule = rule(",
+ " implementation = _dep_rule_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
+ "main_rule(",
+ " name = 'main',",
+ " dep = ':dep_target',",
+ ")",
+ "dep_rule(",
+ " name = 'dep_target',",
+ ")");
+
+ useConfiguration("--experimental_required_aspects");
+ AnalysisResult analysisResult = update("//test:main");
+
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ StarlarkList<?> files = (StarlarkList) configuredTarget.get("my_files");
+ assertThat(Starlark.toIterable(files))
+ .containsExactly("base_aspect can see file required_aspect_file");
+ }
+
+ @Test
+ public void testAspectRequiresAspect_withRequiredAspectProvidersSatisfied() throws Exception {
+ scratch.file(
+ "test/defs.bzl",
+ "prov_a = provider()",
+ "prov_b = provider()",
+ "prov_b_forwarded = provider()",
+ "",
+ "def _aspect_b_impl(target, ctx):",
+ " result = 'aspect_b on target {} '.format(target.label)",
+ " if prov_b in target:",
+ " result += 'found prov_b = {}'.format(target[prov_b].val)",
+ " return struct(aspect_b_result = result,",
+ " providers = [prov_b_forwarded(val = target[prov_b].val)])",
+ " else:",
+ " result += 'cannot find prov_b'",
+ " return struct(aspect_b_result = result)",
+ "aspect_b = aspect(",
+ " implementation = _aspect_b_impl,",
+ " required_aspect_providers = [prov_b]",
+ ")",
+ "",
+ "def _aspect_a_impl(target, ctx):",
+ " result = 'aspect_a on target {} '.format(target.label)",
+ " if prov_a in target:",
+ " result += 'found prov_a = {}'.format(target[prov_a].val)",
+ " else:",
+ " result += 'cannot find prov_a'",
+ " if prov_b_forwarded in target:",
+ " result += ' and found prov_b = {}'.format(target[prov_b_forwarded].val)",
+ " else:",
+ " result += ' but cannot find prov_b'",
+ " return struct(aspect_a_result = result)",
+ "",
+ "aspect_a = aspect(",
+ " implementation = _aspect_a_impl,",
+ " required_aspect_providers = [prov_a],",
+ " attr_aspects = ['dep'],",
+ " requires = [aspect_b]",
+ ")",
+ "",
+ "def _aspect_with_prov_a_impl(target, ctx):",
+ " return [prov_a(val = 'a1')]",
+ "aspect_with_prov_a = aspect(",
+ " implementation = _aspect_with_prov_a_impl,",
+ " provides = [prov_a],",
+ " attr_aspects = ['dep'],",
+ ")",
+ "",
+ "def _aspect_with_prov_b_impl(target, ctx):",
+ " return [prov_b(val = 'b1')]",
+ "aspect_with_prov_b = aspect(",
+ " implementation = _aspect_with_prov_b_impl,",
+ " provides = [prov_b],",
+ " attr_aspects = ['dep'],",
+ ")",
+ "",
+ "def _main_rule_impl(ctx):",
+ " return struct(aspect_a_result = ctx.attr.dep.aspect_a_result,",
+ " aspect_b_result = ctx.attr.dep.aspect_b_result)",
+ "main_rule = rule(",
+ " implementation = _main_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(aspects = [aspect_with_prov_a, aspect_with_prov_b, aspect_a]),",
+ " },",
+ ")",
+ "",
+ "def _dep_rule_impl(ctx):",
+ " pass",
+ "dep_rule = rule(",
+ " implementation = _dep_rule_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
+ "main_rule(",
+ " name = 'main',",
+ " dep = ':dep_target',",
+ ")",
+ "dep_rule(",
+ " name = 'dep_target',",
+ ")");
+
+ useConfiguration("--experimental_required_aspects");
+ AnalysisResult analysisResult = update("//test:main");
+
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ String aspectAResult = (String) configuredTarget.get("aspect_a_result");
+ assertThat(aspectAResult)
+ .isEqualTo("aspect_a on target //test:dep_target found prov_a = a1 and found prov_b = b1");
+
+ String aspectBResult = (String) configuredTarget.get("aspect_b_result");
+ assertThat(aspectBResult).isEqualTo("aspect_b on target //test:dep_target found prov_b = b1");
+ }
+
+ @Test
+ public void testAspectRequiresAspect_withRequiredAspectProvidersNotFound() throws Exception {
+ scratch.file(
+ "test/defs.bzl",
+ "prov_a = provider()",
+ "prov_b = provider()",
+ "",
+ "def _aspect_b_impl(target, ctx):",
+ " result = 'aspect_b on target {} '.format(target.label)",
+ " if prov_b in target:",
+ " result += 'found prov_b = {}'.format(target[prov_b].val)",
+ " else:",
+ " result += 'cannot find prov_b'",
+ " return struct(aspect_b_result = result)",
+ "aspect_b = aspect(",
+ " implementation = _aspect_b_impl,",
+ " required_aspect_providers = [prov_b]",
+ ")",
+ "",
+ "def _aspect_a_impl(target, ctx):",
+ " result = 'aspect_a on target {} '.format(target.label)",
+ " if prov_a in target:",
+ " result += 'found prov_a = {}'.format(target[prov_a].val)",
+ " else:",
+ " result += 'cannot find prov_a'",
+ " return struct(aspect_a_result = result)",
+ "",
+ "aspect_a = aspect(",
+ " implementation = _aspect_a_impl,",
+ " required_aspect_providers = [prov_a],",
+ " attr_aspects = ['dep'],",
+ " requires = [aspect_b]",
+ ")",
+ "",
+ "def _aspect_with_prov_a_impl(target, ctx):",
+ " return [prov_a(val = 'a1')]",
+ "aspect_with_prov_a = aspect(",
+ " implementation = _aspect_with_prov_a_impl,",
+ " provides = [prov_a],",
+ " attr_aspects = ['dep'],",
+ ")",
+ "",
+ "def _main_rule_impl(ctx):",
+ " return struct(aspect_a_result = ctx.attr.dep.aspect_a_result,",
+ " aspect_b_result = ctx.attr.dep.aspect_b_result)",
+ "main_rule = rule(",
+ " implementation = _main_rule_impl,",
+ " attrs = {",
+ " 'dep': attr.label(aspects = [aspect_with_prov_a, aspect_a]),",
+ " },",
+ ")",
+ "",
+ "def _dep_rule_impl(ctx):",
+ " pass",
+ "dep_rule = rule(",
+ " implementation = _dep_rule_impl,",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:defs.bzl', 'main_rule', 'dep_rule')",
+ "main_rule(",
+ " name = 'main',",
+ " dep = ':dep_target',",
+ ")",
+ "dep_rule(",
+ " name = 'dep_target',",
+ ")");
+
+ useConfiguration("--experimental_required_aspects");
+ AnalysisResult analysisResult = update("//test:main");
+
+ ConfiguredTarget configuredTarget =
+ Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+ String aspectAResult = (String) configuredTarget.get("aspect_a_result");
+ assertThat(aspectAResult).isEqualTo("aspect_a on target //test:dep_target found prov_a = a1");
+
+ String aspectBResult = (String) configuredTarget.get("aspect_b_result");
+ assertThat(aspectBResult).isEqualTo("aspect_b on target //test:dep_target cannot find prov_b");
+ }
+
/** StarlarkAspectTest with "keep going" flag */
@RunWith(JUnit4.class)
public static final class WithKeepGoing extends StarlarkDefinedAspectsTest {
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 b6c063d..d408add 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
@@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
import com.google.devtools.build.lib.analysis.starlark.StarlarkAttrModule;
import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleClassFunctions.StarlarkRuleFunction;
@@ -33,6 +34,8 @@
import com.google.devtools.build.lib.events.EventKind;
import com.google.devtools.build.lib.events.NullEventHandler;
import com.google.devtools.build.lib.packages.AdvertisedProviderSet;
+import com.google.devtools.build.lib.packages.Aspect;
+import com.google.devtools.build.lib.packages.AspectClass;
import com.google.devtools.build.lib.packages.AspectParameters;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.BuildType;
@@ -40,6 +43,7 @@
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
import com.google.devtools.build.lib.packages.PredicateWithMessage;
import com.google.devtools.build.lib.packages.RequiredProviders;
+import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
import com.google.devtools.build.lib.packages.StarlarkAspectClass;
@@ -54,6 +58,8 @@
import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
import com.google.devtools.build.lib.testutil.MoreAsserts;
import com.google.devtools.build.lib.util.FileTypeSet;
+import java.util.Arrays;
+import java.util.List;
import javax.annotation.Nullable;
import net.starlark.java.eval.Dict;
import net.starlark.java.eval.EvalException;
@@ -68,7 +74,6 @@
import net.starlark.java.syntax.Program;
import net.starlark.java.syntax.StarlarkFile;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
@@ -90,7 +95,7 @@
ev.setSemantics(options); // for StarlarkThread
}
- @Rule public ExpectedException thrown = ExpectedException.none();
+ @org.junit.Rule public ExpectedException thrown = ExpectedException.none();
@Before
public final void createBuildFile() throws Exception {
@@ -407,6 +412,881 @@
}
@Test
+ public void testAttrWithAspectRequiringAspects_stackOfRequiredAspects() throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl, requires = [aspect_c])",
+ "aspect_a = aspect(implementation = _impl, requires = [aspect_b])",
+ "a = attr.label_list(aspects = [aspect_a])");
+ StarlarkAttrModule.Descriptor attr = (StarlarkAttrModule.Descriptor) ev.lookup("a");
+
+ StarlarkDefinedAspect aspectA = (StarlarkDefinedAspect) ev.lookup("aspect_a");
+ assertThat(aspectA).isNotNull();
+ StarlarkDefinedAspect aspectB = (StarlarkDefinedAspect) ev.lookup("aspect_b");
+ assertThat(aspectB).isNotNull();
+ StarlarkDefinedAspect aspectC = (StarlarkDefinedAspect) ev.lookup("aspect_c");
+ assertThat(aspectC).isNotNull();
+ List<AspectClass> expectedAspects =
+ Arrays.asList(aspectA.getAspectClass(), aspectB.getAspectClass(), aspectC.getAspectClass());
+ assertThat(attr.build("xxx").getAspectClasses()).containsExactlyElementsIn(expectedAspects);
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_aspectRequiredByMultipleAspects()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl, requires = [aspect_c])",
+ "aspect_a = aspect(implementation = _impl, requires = [aspect_c])",
+ "a = attr.label_list(aspects = [aspect_a, aspect_b])");
+ StarlarkAttrModule.Descriptor attr = (StarlarkAttrModule.Descriptor) ev.lookup("a");
+
+ StarlarkDefinedAspect aspectA = (StarlarkDefinedAspect) ev.lookup("aspect_a");
+ assertThat(aspectA).isNotNull();
+ StarlarkDefinedAspect aspectB = (StarlarkDefinedAspect) ev.lookup("aspect_b");
+ assertThat(aspectB).isNotNull();
+ StarlarkDefinedAspect aspectC = (StarlarkDefinedAspect) ev.lookup("aspect_c");
+ assertThat(aspectC).isNotNull();
+ List<AspectClass> expectedAspects =
+ Arrays.asList(aspectA.getAspectClass(), aspectB.getAspectClass(), aspectC.getAspectClass());
+ assertThat(attr.build("xxx").getAspectClasses()).containsExactlyElementsIn(expectedAspects);
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_aspectRequiredByMultipleAspects2()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_d = aspect(implementation = _impl)",
+ "aspect_c = aspect(implementation = _impl, requires = [aspect_d])",
+ "aspect_b = aspect(implementation = _impl, requires = [aspect_d])",
+ "aspect_a = aspect(implementation = _impl, requires = [aspect_b, aspect_c])",
+ "a = attr.label_list(aspects = [aspect_a])");
+ StarlarkAttrModule.Descriptor attr = (StarlarkAttrModule.Descriptor) ev.lookup("a");
+
+ StarlarkDefinedAspect aspectA = (StarlarkDefinedAspect) ev.lookup("aspect_a");
+ assertThat(aspectA).isNotNull();
+ StarlarkDefinedAspect aspectB = (StarlarkDefinedAspect) ev.lookup("aspect_b");
+ assertThat(aspectB).isNotNull();
+ StarlarkDefinedAspect aspectC = (StarlarkDefinedAspect) ev.lookup("aspect_c");
+ assertThat(aspectC).isNotNull();
+ StarlarkDefinedAspect aspectD = (StarlarkDefinedAspect) ev.lookup("aspect_d");
+ assertThat(aspectD).isNotNull();
+ List<AspectClass> expectedAspects =
+ Arrays.asList(
+ aspectA.getAspectClass(),
+ aspectB.getAspectClass(),
+ aspectC.getAspectClass(),
+ aspectD.getAspectClass());
+ assertThat(attr.build("xxx").getAspectClasses()).containsExactlyElementsIn(expectedAspects);
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_requireExistingAspect_passed() throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_b = aspect(implementation = _impl)",
+ "aspect_a = aspect(implementation = _impl, requires = [aspect_b])",
+ "a = attr.label_list(aspects = [aspect_b, aspect_a])");
+ StarlarkAttrModule.Descriptor attr = (StarlarkAttrModule.Descriptor) ev.lookup("a");
+
+ StarlarkDefinedAspect aspectA = (StarlarkDefinedAspect) ev.lookup("aspect_a");
+ assertThat(aspectA).isNotNull();
+ StarlarkDefinedAspect aspectB = (StarlarkDefinedAspect) ev.lookup("aspect_b");
+ assertThat(aspectB).isNotNull();
+ List<AspectClass> expectedAspects =
+ Arrays.asList(aspectA.getAspectClass(), aspectB.getAspectClass());
+ assertThat(attr.build("xxx").getAspectClasses()).containsExactlyElementsIn(expectedAspects);
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_requireExistingAspect_failed() throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ ev.setFailFast(false);
+
+ evalAndExport(
+ ev,
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_b = aspect(implementation = _impl)",
+ "aspect_a = aspect(implementation = _impl, requires = [aspect_b])",
+ "attr.label_list(aspects = [aspect_a, aspect_b])");
+
+ ev.assertContainsError(
+ String.format(
+ "aspect %s%%aspect_b was added before as a required aspect of aspect %s%%aspect_a",
+ FAKE_LABEL, FAKE_LABEL));
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritDefaultValues() throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_b = aspect(implementation = _impl)",
+ "aspect_a = aspect(implementation = _impl, requires = [aspect_b])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl, attrs={'a': attr.label_list(aspects = [aspect_a])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ // aspect_b inherits the required providers and propagation attributes from aspect_a
+ Aspect aspectB = aspects.get(0);
+ assertThat(aspectB.getDescriptor().getInheritedRequiredProviders())
+ .isEqualTo(RequiredProviders.acceptAnyBuilder().build());
+ assertThat(aspectB.getDescriptor().getInheritedAttributeAspects()).isEmpty();
+
+ // aspect_a is not required by any other aspect so its does not inherit anything
+ Aspect aspectA = aspects.get(1);
+ assertThat(aspectA.getDescriptor().getInheritedRequiredProviders())
+ .isEqualTo(RequiredProviders.acceptNoneBuilder().build());
+ assertThat(aspectA.getDescriptor().getInheritedAttributeAspects()).isEmpty();
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritAttrAspectsFromSingleAspect()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_b = aspect(implementation = _impl,",
+ " attr_aspects = ['extra_deps'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_b],",
+ " attr_aspects = ['deps'])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl,",
+ " attrs = {'a': attr.label_list(aspects = [aspect_a])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectB = aspects.get(0);
+ assertThat(aspectB.getDescriptor().getInheritedAttributeAspects()).containsExactly("deps");
+ assertThat(aspectB.getDefinition().getRestrictToAttributes()).containsExactly("extra_deps");
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritRequiredProvidersFromSingleAspect()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_b = aspect(implementation = _impl,",
+ " required_providers= ['go'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_b],",
+ " required_providers=[['java', cc], ['python']])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl,",
+ " attrs = {'a': attr.label_list(aspects = [aspect_a])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectB = aspects.get(0);
+ assertThat(aspectB.getDescriptor().getInheritedRequiredProviders().getDescription())
+ .isEqualTo("['java', 'cc'] or 'python'");
+ assertThat(aspectB.getDefinition().getRequiredProviders().getDescription()).isEqualTo("'go'");
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringExistingAspect_inheritAttrAspectsFromSingleAspect()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_b = aspect(implementation = _impl)",
+ "aspect_a = aspect(implementation = _impl, requires = [aspect_b],",
+ " attr_aspects = ['deps'])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl,",
+ " attrs={'a': attr.label_list(aspects = [aspect_b, aspect_a])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectB = aspects.get(0);
+ assertThat(aspectB.getDescriptor().getInheritedAttributeAspects()).containsExactly("deps");
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringExistingAspect_inheritRequiredProvidersFromSingleAspect()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_b = aspect(implementation = _impl)",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_b],",
+ " required_providers=[['java', cc], ['python']])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl, attrs={'a': attr.label_list(aspects = [aspect_b, aspect_a])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectB = aspects.get(0);
+ assertThat(aspectB.getDescriptor().getInheritedRequiredProviders().getDescription())
+ .isEqualTo("['java', 'cc'] or 'python'");
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritAttrAspectsFromMultipleAspects()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " attr_aspects = ['extra_deps'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " attr_aspects = ['deps'])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl,",
+ " attrs={'a': attr.label_list(aspects = [aspect_a, aspect_b])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getDescriptor().getInheritedAttributeAspects())
+ .containsExactly("deps", "extra_deps");
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritRequiredProvidersFromMultipleAspects()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " required_providers=['go'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " required_providers=[['java', cc], ['python']])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl,",
+ " attrs={'a': attr.label_list(aspects = [aspect_a, aspect_b])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getDescriptor().getInheritedRequiredProviders().getDescription())
+ .isEqualTo("['java', 'cc'] or 'python' or 'go'");
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritAllAttrAspects() throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " attr_aspects = ['extra_deps'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " attr_aspects = ['*'])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl, attrs={'a': attr.label_list(aspects = [aspect_a, aspect_b])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getDescriptor().getInheritedAttributeAspects())
+ .isNull(); // propagate along all attributes '*'
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritAllRequiredProviders() throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " required_providers = [])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " required_providers=[['java', cc], ['python']])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl, attrs={'a': attr.label_list(aspects = [aspect_a, aspect_b])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getDescriptor().getInheritedRequiredProviders())
+ .isEqualTo(RequiredProviders.acceptAnyBuilder().build());
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritAttrAspectsFromAspectsStack()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " attr_aspects = ['extra_deps'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_b],",
+ " attr_aspects = ['deps'])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl, attrs={'a': attr.label_list(aspects = [aspect_a])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getDescriptor().getInheritedAttributeAspects())
+ .containsExactly("deps", "extra_deps");
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritRequiredProvidersFromAspectsStack()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " required_providers=['go'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_b],",
+ " required_providers=[['java', cc], ['python']])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl, attrs={'a': attr.label_list(aspects = [aspect_a])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getDescriptor().getInheritedRequiredProviders().getDescription())
+ .isEqualTo("['java', 'cc'] or 'python' or 'go'");
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritAllAttrAspectsFromAspectsStack()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " attr_aspects = ['*'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_b],",
+ " attr_aspects = ['deps'])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl, attrs={'a': attr.label_list(aspects = [aspect_a])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getDescriptor().getInheritedAttributeAspects())
+ .isNull(); // propagate along all attributes '*'
+ }
+
+ @Test
+ public void testAttrWithAspectRequiringAspects_inheritAllRequiredProvidersFromAspectsStack()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "cc = provider()",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " required_providers=[cc])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_b],",
+ " required_providers=[])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl, attrs={'a': attr.label_list(aspects = [aspect_a])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute a = rule.getRuleClassObject().getAttributeByName("a");
+ ImmutableList<Aspect> aspects = a.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getDescriptor().getInheritedRequiredProviders())
+ .isEqualTo(RequiredProviders.acceptAnyBuilder().build());
+ }
+
+ /**
+ * An aspect required by different aspects in different attributes inherits the correct
+ * propagation attributes.
+ */
+ @Test
+ public void testAttrWithAspectRequiringAspects_aspectRequiredInMultipleAttrs_inheritAttrAspects()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " attr_aspects = ['deps'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " attr_aspects = ['extra_deps'])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl,",
+ " attrs={'attr1': attr.label_list(aspects = [aspect_a]),",
+ " 'attr2': attr.label_list(aspects = [aspect_b]),",
+ " 'attr3': attr.label_list(aspects = [aspect_c])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute attr1 = rule.getRuleClassObject().getAttributeByName("attr1");
+ ImmutableList<Aspect> aspects = attr1.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDescriptor().getInheritedAttributeAspects())
+ .containsExactly("extra_deps");
+
+ Attribute attr2 = rule.getRuleClassObject().getAttributeByName("attr2");
+ aspects = attr2.getAspects(rule);
+ aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDescriptor().getInheritedAttributeAspects()).containsExactly("deps");
+
+ Attribute attr3 = rule.getRuleClassObject().getAttributeByName("attr3");
+ aspects = attr3.getAspects(rule);
+ aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDescriptor().getInheritedAttributeAspects()).isEmpty();
+ }
+
+ /**
+ * An aspect required by different aspects in different attributes inherits the correct required
+ * providers.
+ */
+ @Test
+ public void
+ testAttrWithAspectRequiringAspects_aspectRequiredInMultipleAttrs_inheritRequiredProviders()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " required_providers=['prov1', 'prov2'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " required_providers=[['prov3', 'prov4'], ['prov2']])",
+ "def impl(ctx):",
+ " return None",
+ "my_rule = rule(impl,",
+ " attrs={'attr1': attr.label_list(aspects = [aspect_a]),",
+ " 'attr2': attr.label_list(aspects = [aspect_b]),",
+ " 'attr3': attr.label_list(aspects = [aspect_c])})");
+ scratch.file("BUILD", "load(':lib.bzl', 'my_rule')", "my_rule(name = 'main')");
+
+ RuleContext ruleContext = createRuleContext("//:main").getRuleContext();
+
+ Rule rule = ruleContext.getRule();
+ Attribute attr1 = rule.getRuleClassObject().getAttributeByName("attr1");
+ ImmutableList<Aspect> aspects = attr1.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDescriptor().getInheritedRequiredProviders().getDescription())
+ .isEqualTo("['prov3', 'prov4'] or 'prov2'");
+
+ Attribute attr2 = rule.getRuleClassObject().getAttributeByName("attr2");
+ aspects = attr2.getAspects(rule);
+ aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDescriptor().getInheritedRequiredProviders().getDescription())
+ .isEqualTo("['prov1', 'prov2']");
+
+ Attribute attr3 = rule.getRuleClassObject().getAttributeByName("attr3");
+ aspects = attr3.getAspects(rule);
+ aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDescriptor().getInheritedRequiredProviders())
+ .isEqualTo(RequiredProviders.acceptNoneBuilder().build());
+ }
+
+ /**
+ * An aspect required by different aspects in different rules inherits the correct propagation
+ * attributes.
+ */
+ @Test
+ public void testAttrWithAspectRequiringAspects_aspectRequiredInMultipleRules_inheritAttrAspects()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " attr_aspects = ['deps'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " attr_aspects = ['extra_deps'])",
+ "def impl(ctx):",
+ " return None",
+ "rule_1 = rule(impl,",
+ " attrs={'attr': attr.label_list(aspects = [aspect_a])})",
+ "rule_2 = rule(impl,",
+ " attrs={'attr': attr.label_list(aspects = [aspect_b])})");
+ scratch.file(
+ "BUILD",
+ "load(':lib.bzl', 'rule_1', 'rule_2')",
+ "rule_1(name = 't1')",
+ "rule_2(name = 't2')");
+
+ RuleContext ruleContext1 = createRuleContext("//:t1").getRuleContext();
+ RuleContext ruleContext2 = createRuleContext("//:t2").getRuleContext();
+
+ Rule rule = ruleContext1.getRule();
+ Attribute attr = rule.getRuleClassObject().getAttributeByName("attr");
+ ImmutableList<Aspect> aspects = attr.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDescriptor().getInheritedAttributeAspects())
+ .containsExactly("extra_deps");
+
+ rule = ruleContext2.getRule();
+ attr = rule.getRuleClassObject().getAttributeByName("attr");
+ aspects = attr.getAspects(rule);
+ aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDescriptor().getInheritedAttributeAspects()).containsExactly("deps");
+ }
+
+ /**
+ * An aspect required by different aspects in different rules inherits the correct required
+ * providers.
+ */
+ @Test
+ public void
+ testAttrWithAspectRequiringAspects_aspectRequiredInMultipleRules_inheritRequiredProviders()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl)",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " required_providers=['prov1', 'prov2'])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_c],",
+ " required_providers=[['prov3', 'prov4'], ['prov2']])",
+ "def impl(ctx):",
+ " return None",
+ "rule_1 = rule(impl,",
+ " attrs={'attr': attr.label_list(aspects = [aspect_a])})",
+ "rule_2 = rule(impl,",
+ " attrs={'attr': attr.label_list(aspects = [aspect_b])})");
+ scratch.file(
+ "BUILD",
+ "load(':lib.bzl', 'rule_1', 'rule_2')",
+ "rule_1(name = 't1')",
+ "rule_2(name = 't2')");
+
+ RuleContext ruleContext1 = createRuleContext("//:t1").getRuleContext();
+ RuleContext ruleContext2 = createRuleContext("//:t2").getRuleContext();
+
+ Rule rule = ruleContext1.getRule();
+ Attribute attr = rule.getRuleClassObject().getAttributeByName("attr");
+ ImmutableList<Aspect> aspects = attr.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDescriptor().getInheritedRequiredProviders().getDescription())
+ .isEqualTo("['prov3', 'prov4'] or 'prov2'");
+
+ rule = ruleContext2.getRule();
+ attr = rule.getRuleClassObject().getAttributeByName("attr");
+ aspects = attr.getAspects(rule);
+ aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDescriptor().getInheritedRequiredProviders().getDescription())
+ .isEqualTo("['prov1', 'prov2']");
+ }
+
+ /**
+ * An aspect required by different aspects in different rules gets the correct parameters values
+ * from its base rule.
+ */
+ @Test
+ public void testAttrWithAspectRequiringAspects_aspectRequiredInMultipleRules_getParametersValues()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_c = aspect(implementation = _impl,",
+ " attrs = {'param': attr.string(values = ['v1', 'v2', 'v3'])})",
+ "aspect_b = aspect(implementation = _impl,",
+ " requires = [aspect_c])",
+ "aspect_a = aspect(implementation = _impl,",
+ " requires = [aspect_c])",
+ "def impl(ctx):",
+ " return None",
+ "rule_1 = rule(impl,",
+ " attrs={'attr': attr.label_list(aspects = [aspect_a]),",
+ " 'param': attr.string()})",
+ "rule_2 = rule(impl,",
+ " attrs={'attr': attr.label_list(aspects = [aspect_b]),",
+ " 'param': attr.string()})");
+ scratch.file(
+ "BUILD",
+ "load(':lib.bzl', 'rule_1', 'rule_2')",
+ "rule_1(name = 't1', param = 'v1')",
+ "rule_2(name = 't2', param = 'v2')");
+
+ RuleContext ruleContext1 = createRuleContext("//:t1").getRuleContext();
+ RuleContext ruleContext2 = createRuleContext("//:t2").getRuleContext();
+
+ Rule rule = ruleContext1.getRule();
+ Attribute attr = rule.getRuleClassObject().getAttributeByName("attr");
+ ImmutableList<Aspect> aspects = attr.getAspects(rule);
+ Aspect aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDefinition().getAttributes().get("param").getDefaultValueUnchecked())
+ .isEqualTo("v1");
+
+ rule = ruleContext2.getRule();
+ attr = rule.getRuleClassObject().getAttributeByName("attr");
+ aspects = attr.getAspects(rule);
+ aspectC = aspects.get(0);
+ assertThat(aspectC.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_c");
+ assertThat(aspectC.getDefinition().getAttributes().get("param").getDefaultValueUnchecked())
+ .isEqualTo("v2");
+ }
+
+ /**
+ * Using the same attribute in multiple rules should not change the required aspect inherited
+ * propagation attributes.
+ */
+ @Test
+ public void testAttrWithAspectRequiringAspects_sameAttrInMultipleRules_inheritSameAttrAspects()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_b = aspect(implementation = _impl)",
+ "aspect_a = aspect(implementation = _impl, requires = [aspect_b],",
+ " attr_aspects = ['extra_deps'])",
+ "my_attr = attr.label_list(aspects = [aspect_a])",
+ "def impl(ctx):",
+ " return None",
+ "rule_1 = rule(impl,",
+ " attrs={'attr': my_attr})",
+ "rule_2 = rule(impl,",
+ " attrs={'attr': my_attr})");
+ scratch.file(
+ "BUILD",
+ "load(':lib.bzl', 'rule_1', 'rule_2')",
+ "rule_1(name = 't1')",
+ "rule_2(name = 't2')");
+
+ RuleContext ruleContext1 = createRuleContext("//:t1").getRuleContext();
+ RuleContext ruleContext2 = createRuleContext("//:t2").getRuleContext();
+
+ Rule rule = ruleContext1.getRule();
+ Attribute attr = rule.getRuleClassObject().getAttributeByName("attr");
+ ImmutableList<Aspect> aspects = attr.getAspects(rule);
+ Aspect aspectB = aspects.get(0);
+ assertThat(aspectB.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_b");
+ assertThat(aspectB.getDescriptor().getInheritedAttributeAspects())
+ .containsExactly("extra_deps");
+
+ rule = ruleContext2.getRule();
+ attr = rule.getRuleClassObject().getAttributeByName("attr");
+ aspects = attr.getAspects(rule);
+ aspectB = aspects.get(0);
+ assertThat(aspectB.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_b");
+ assertThat(aspectB.getDescriptor().getInheritedAttributeAspects())
+ .containsExactly("extra_deps");
+ }
+
+ /**
+ * Using the same attribute in multiple rules should not change the required aspect inherited
+ * required providers.
+ */
+ @Test
+ public void
+ testAttrWithAspectRequiringAspects_sameAttrInMultipleRules_inheritSameRequiredProviders()
+ throws Exception {
+ setBuildLanguageOptions("--experimental_required_aspects=true");
+ scratch.file(
+ "lib.bzl",
+ "def _impl(target, ctx):",
+ " pass",
+ "aspect_b = aspect(implementation = _impl)",
+ "aspect_a = aspect(implementation = _impl, requires = [aspect_b],",
+ " required_providers=[['prov3', 'prov4'], ['prov2']])",
+ "my_attr = attr.label_list(aspects = [aspect_a])",
+ "def impl(ctx):",
+ " return None",
+ "rule_1 = rule(impl,",
+ " attrs={'attr': my_attr})",
+ "rule_2 = rule(impl,",
+ " attrs={'attr': my_attr})");
+ scratch.file(
+ "BUILD",
+ "load(':lib.bzl', 'rule_1', 'rule_2')",
+ "rule_1(name = 't1')",
+ "rule_2(name = 't2')");
+
+ RuleContext ruleContext1 = createRuleContext("//:t1").getRuleContext();
+ RuleContext ruleContext2 = createRuleContext("//:t2").getRuleContext();
+
+ Rule rule = ruleContext1.getRule();
+ Attribute attr = rule.getRuleClassObject().getAttributeByName("attr");
+ ImmutableList<Aspect> aspects = attr.getAspects(rule);
+ Aspect aspectB = aspects.get(0);
+ assertThat(aspectB.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_b");
+ assertThat(aspectB.getDescriptor().getInheritedRequiredProviders().getDescription())
+ .isEqualTo("['prov3', 'prov4'] or 'prov2'");
+
+ rule = ruleContext2.getRule();
+ attr = rule.getRuleClassObject().getAttributeByName("attr");
+ aspects = attr.getAspects(rule);
+ aspectB = aspects.get(0);
+ assertThat(aspectB.getAspectClass().getName()).isEqualTo("//:lib.bzl%aspect_b");
+ assertThat(aspectB.getDescriptor().getInheritedRequiredProviders().getDescription())
+ .isEqualTo("['prov3', 'prov4'] or 'prov2'");
+ }
+
+ @Test
public void testAspectExtraDeps() throws Exception {
evalAndExport(
ev,
diff --git a/src/test/shell/integration/aspect_test.sh b/src/test/shell/integration/aspect_test.sh
index 63f0c97..12dd171 100755
--- a/src/test/shell/integration/aspect_test.sh
+++ b/src/test/shell/integration/aspect_test.sh
@@ -441,4 +441,164 @@
expect_not_log "my_aspect runs on target //${package}:target_with_c_indeps_not_reached"
}
+function test_aspects_propagating_other_aspects() {
+ local package="a"
+ mkdir -p "${package}"
+
+ cat > "${package}/lib.bzl" <<EOF
+prov_a = provider()
+
+def _required_aspect_impl(target, ctx):
+ print("required_aspect runs on target {}".format(target.label))
+ return []
+
+required_aspect = aspect(implementation = _required_aspect_impl,
+ required_providers = [prov_a])
+
+def _base_aspect_impl(target, ctx):
+ print("base_aspect runs on target {}".format(target.label))
+ return []
+
+base_aspect = aspect(implementation = _base_aspect_impl,
+ attr_aspects = ['dep'],
+ requires = [required_aspect])
+
+def _rule_impl(ctx):
+ pass
+
+base_rule = rule(implementation = _rule_impl,
+ attrs = {'dep': attr.label(aspects=[base_aspect])})
+
+dep_rule_without_providers = rule(implementation = _rule_impl,
+ attrs = {'dep': attr.label()})
+
+def _dep_rule_with_prov_a_impl(ctx):
+ return [prov_a()]
+
+dep_rule_with_prov_a = rule(implementation = _dep_rule_with_prov_a_impl,
+ provides = [prov_a])
+
+EOF
+
+ cat > "${package}/BUILD" <<EOF
+load('//${package}:lib.bzl', 'base_rule', 'dep_rule_without_providers',
+ 'dep_rule_with_prov_a')
+
+base_rule(
+ name = 'main',
+ dep = ':dep_target_without_providers',
+)
+
+dep_rule_without_providers(
+ name = 'dep_target_without_providers',
+ dep = ':dep_target_without_providers_1'
+)
+
+dep_rule_without_providers(
+ name = 'dep_target_without_providers_1',
+ dep = ':dep_target_with_prov_a'
+)
+
+dep_rule_with_prov_a(
+ name = 'dep_target_with_prov_a',
+)
+
+EOF
+
+ bazel build "${package}:main" \
+ --experimental_required_aspects &>"$TEST_log" \
+ || fail "Build failed but should have succeeded"
+
+ # base_aspect will run on dep_target_without_providers,
+ # dep_target_without_providers_1 and dep_target_with_prov_a but
+ # required_aspect will only run on dep_target_with_prov_a because
+ # it satisfies its required providers
+ expect_log "base_aspect runs on target //${package}:dep_target_with_prov_a"
+ expect_log "base_aspect runs on target //${package}:dep_target_without_providers_1"
+ expect_log "base_aspect runs on target //${package}:dep_target_without_providers"
+ expect_log "required_aspect runs on target //${package}:dep_target_with_prov_a"
+ expect_not_log "required_aspect runs on target //${package}:dep_target_without_providers_1"
+ expect_not_log "required_aspect runs on target //${package}:dep_target_without_providers/"
+}
+
+function test_aspects_propagating_other_aspects_stack_of_required_aspects() {
+ local package="pkg"
+ mkdir -p "${package}"
+
+ cat > "${package}/lib.bzl" <<EOF
+
+def _aspect_a_impl(target, ctx):
+ print("Aspect 'a' applied on target: {}".format(target.label))
+ return []
+
+def _aspect_b_impl(target, ctx):
+ print("Aspect 'b' applied on target: {}".format(target.label))
+ return []
+
+def _aspect_c_impl(target, ctx):
+ print("Aspect 'c' applied on target: {}".format(target.label))
+ return []
+
+def r_impl(ctx):
+ return []
+
+aspect_c = aspect(implementation = _aspect_c_impl,
+ attr_aspects = ["deps"])
+
+aspect_b = aspect(implementation = _aspect_b_impl,
+ attr_aspects = ["deps"], requires = [aspect_c])
+
+aspect_a = aspect(implementation = _aspect_a_impl,
+ attr_aspects = ["deps"], requires = [aspect_b])
+
+rule_r = rule(
+ implementation = r_impl,
+ attrs = {
+ "deps": attr.label_list(aspects = [aspect_a]),
+ }
+)
+
+EOF
+
+echo "inline int x() { return 42; }" > "${package}/x.h"
+ cat > "${package}/t.cc" <<EOF
+#include "${package}/x.h"
+
+int a() { return x(); }
+EOF
+ cat > "${package}/BUILD" <<EOF
+load("//${package}:lib.bzl", "rule_r")
+
+cc_library(
+ name = "x",
+ hdrs = ["x.h"],
+)
+
+cc_library(
+ name = "t",
+ srcs = ["t.cc"],
+ deps = [":x"],
+)
+
+rule_r(
+ name = "test",
+ deps = [":t"],
+)
+EOF
+
+ bazel build "${package}:test" \
+ --experimental_required_aspects &>"$TEST_log" \
+ || fail "Build failed but should have succeeded"
+
+ # Check that aspects: aspect_a, aspect_b, aspect_c were propagated to the
+ # dependencies of target test: t and x when only aspect_a is specified
+ # in the rule definition
+ expect_log_once "Aspect 'c' applied on target: //${package}:x"
+ expect_log_once "Aspect 'c' applied on target: //${package}:t"
+ expect_log_once "Aspect 'b' applied on target: //${package}:x"
+ expect_log_once "Aspect 'b' applied on target: //${package}:t"
+ expect_log_once "Aspect 'a' applied on target: //${package}:x"
+ expect_log_once "Aspect 'a' applied on target: //${package}:t"
+}
+
run_suite "Tests for aspects"