// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.packages; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.packages.ConfigurationFragmentPolicy.MissingFragmentPolicy; import com.google.devtools.build.lib.packages.NativeAspectClass.NativeAspectFactory; import com.google.devtools.build.lib.util.Preconditions; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * The definition of an aspect (see {@link Aspect} for moreinformation.) * *

Contains enough information to build up the configured target graph except for the actual way * to build the Skyframe node (that is the territory of * {@link com.google.devtools.build.lib.view AspectFactory}). In particular: *

* *

The way to build the Skyframe node is not here because this data needs to be accessible from * the {@code .packages} package and that one requires references to the {@code .view} package. */ @Immutable public final class AspectDefinition { private final String name; private final ImmutableSet> requiredProviders; private final ImmutableSet requiredProviderNames; private final ImmutableMap attributes; private final ImmutableMultimap attributeAspects; @Nullable private final ConfigurationFragmentPolicy configurationFragmentPolicy; private AspectDefinition( String name, ImmutableSet> requiredProviders, ImmutableMap attributes, ImmutableMultimap attributeAspects, @Nullable ConfigurationFragmentPolicy configurationFragmentPolicy) { this.name = name; this.requiredProviders = requiredProviders; this.requiredProviderNames = toStringSet(requiredProviders); this.attributes = attributes; this.attributeAspects = attributeAspects; this.configurationFragmentPolicy = configurationFragmentPolicy; } public String getName() { return name; } /** * Returns the attributes of the aspect in the form of a String -> {@link Attribute} map. * *

All attributes are either implicit or late-bound. */ public ImmutableMap getAttributes() { return attributes; } /** * Returns the set of {@link com.google.devtools.build.lib.analysis.TransitiveInfoProvider} * instances that must be present on a configured target so that this aspect can be applied to it. * *

We cannot refer to that class here due to our dependency structure, so this returns a set * of unconstrained class objects. * *

If a configured target does not have a required provider, the aspect is silently not created * for it. */ public ImmutableSet> getRequiredProviders() { return requiredProviders; } /** * Returns the set of class names of * {@link com.google.devtools.build.lib.analysis.TransitiveInfoProvider} instances that must be * present on a configured target so that this aspect can be applied to it. * *

This set is a mirror of the set returned by {@link #getRequiredProviders}, but contains the * names of the classes rather than the class objects themselves. * *

If a configured target does not have a required provider, the aspect is silently not created * for it. */ public ImmutableSet getRequiredProviderNames() { return requiredProviderNames; } /** * Returns the attribute -> set of required aspects map. */ public ImmutableMultimap getAttributeAspects() { return attributeAspects; } /** * Returns the set of configuration fragments required by this Aspect, or {@code null} if it has * not set a configuration fragment policy, meaning it should inherit from the attached rule. */ @Nullable public ConfigurationFragmentPolicy getConfigurationFragmentPolicy() { // TODO(mstaib): When all existing aspects properly set their configuration fragment policy, // this method and the associated member should no longer be nullable. // "inherit from the attached rule" should go away. return configurationFragmentPolicy; } /** * Returns the attribute -> set of labels that are provided by aspects of attribute. */ public static ImmutableMultimap visitAspectsIfRequired( Target from, Attribute attribute, Target to, DependencyFilter dependencyFilter) { // Aspect can be declared only for Rules. if (!(from instanceof Rule) || !(to instanceof Rule)) { return ImmutableMultimap.of(); } RuleClass ruleClass = ((Rule) to).getRuleClassObject(); ImmutableSet> providers = ruleClass.getAdvertisedProviders(); return visitAspectsIfRequired((Rule) from, attribute, toStringSet(providers), dependencyFilter); } /** * Returns the attribute -> set of labels that are provided by aspects of attribute. */ public static ImmutableMultimap visitAspectsIfRequired( Rule from, Attribute attribute, Set advertisedProviders, DependencyFilter dependencyFilter) { if (advertisedProviders.isEmpty()) { return ImmutableMultimap.of(); } LinkedHashMultimap result = LinkedHashMultimap.create(); for (Aspect candidateClass : attribute.getAspects(from)) { // Check if target satisfies condition for this aspect (has to provide all required // TransitiveInfoProviders) if (!advertisedProviders.containsAll( candidateClass.getDefinition().getRequiredProviderNames())) { continue; } addAllAttributesOfAspect(from, result, candidateClass, dependencyFilter); } return ImmutableMultimap.copyOf(result); } private static ImmutableSet toStringSet(ImmutableSet> classes) { ImmutableSet.Builder classStrings = new ImmutableSet.Builder<>(); for (Class clazz : classes) { classStrings.add(clazz.getName()); } return classStrings.build(); } /** * Collects all attribute labels from the specified aspectDefinition. */ public static void addAllAttributesOfAspect( Rule from, Multimap labelBuilder, Aspect aspect, DependencyFilter dependencyFilter) { ImmutableMap attributes = aspect.getDefinition().getAttributes(); for (Attribute aspectAttribute : attributes.values()) { if (!dependencyFilter.apply(aspect, aspectAttribute)) { continue; } if (aspectAttribute.getType() == BuildType.LABEL) { Label label = from.getLabel().resolveRepositoryRelative( BuildType.LABEL.cast(aspectAttribute.getDefaultValue(from))); if (label != null) { labelBuilder.put(aspectAttribute, label); } } else if (aspectAttribute.getType() == BuildType.LABEL_LIST) { for (Label label : BuildType.LABEL_LIST.cast(aspectAttribute.getDefaultValue(from))) { labelBuilder.put(aspectAttribute, from.getLabel().resolveRepositoryRelative(label)); } } } } /** * Builder class for {@link AspectDefinition}. */ public static final class Builder { private final String name; private final Map attributes = new LinkedHashMap<>(); private final Set> requiredProviders = new LinkedHashSet<>(); private final Multimap attributeAspects = LinkedHashMultimap.create(); private final ConfigurationFragmentPolicy.Builder configurationFragmentPolicy = new ConfigurationFragmentPolicy.Builder(); // TODO(mstaib): When all existing aspects properly set their configuration fragment policy, // remove this flag and the code that interacts with it. /** * True if the aspect definition has intentionally specified a configuration fragment policy by * calling any of the methods which set up the policy, and thus needs the built AspectDefinition * to retain the policy. */ private boolean hasConfigurationFragmentPolicy = false; public Builder(String name) { this.name = name; } /** * Asserts that this aspect can only be evaluated for rules that supply the specified provider. */ public Builder requireProvider(Class requiredProvider) { this.requiredProviders.add(requiredProvider); return this; } /** * Declares that this aspect depends on the given aspects in {@code aspectFactories} provided * by direct dependencies through attribute {@code attribute} on the target associated with this * aspect. * *

Note that {@code ConfiguredAspectFactory} instances are expected in the second argument, * but we cannot reference that interface here. */ @SafeVarargs public final Builder attributeAspect( String attribute, Class... aspectFactories) { Preconditions.checkNotNull(attribute); for (Class aspectFactory : aspectFactories) { this .attributeAspect( attribute, new NativeAspectClass<>(Preconditions.checkNotNull(aspectFactory))); } return this; } /** * Declares that this aspect depends on the given {@link AspectClass} provided * by direct dependencies through attribute {@code attribute} on the target associated with this * aspect. */ public final Builder attributeAspect(String attribute, AspectClass aspectClass) { Preconditions.checkNotNull(attribute); this.attributeAspects.put(attribute, Preconditions.checkNotNull(aspectClass)); return this; } /** * Adds an attribute to the aspect. * *

Since aspects do not appear in BUILD files, the attribute must be either implicit * (not available in the BUILD file, starting with '$') or late-bound (determined after the * configuration is available, starting with ':') */ public Builder add(Attribute.Builder attr) { Attribute attribute = attr.build(); return add(attribute); } /** * Adds an attribute to the aspect. * *

Since aspects do not appear in BUILD files, the attribute must be either implicit * (not available in the BUILD file, starting with '$') or late-bound (determined after the * configuration is available, starting with ':') */ public Builder add(Attribute attribute) { Preconditions.checkArgument(attribute.isImplicit() || attribute.isLateBound()); Preconditions.checkArgument(!attributes.containsKey(attribute.getName()), "An attribute with the name '%s' already exists.", attribute.getName()); attributes.put(attribute.getName(), attribute); return this; } /** * Declares that the implementation of the associated aspect definition requires the given * fragments to be present in this rule's host and target configurations. * *

The value is inherited by subclasses. */ public Builder requiresConfigurationFragments(Class... configurationFragments) { hasConfigurationFragmentPolicy = true; configurationFragmentPolicy .requiresConfigurationFragments(ImmutableSet.copyOf(configurationFragments)); return this; } /** * Declares that the implementation of the associated aspect definition requires the given * fragments to be present in the host configuration. * *

The value is inherited by subclasses. */ public Builder requiresHostConfigurationFragments(Class... configurationFragments) { hasConfigurationFragmentPolicy = true; configurationFragmentPolicy .requiresHostConfigurationFragments(ImmutableSet.copyOf(configurationFragments)); return this; } /** * Declares the configuration fragments that are required by this rule for the target * configuration. * *

In contrast to {@link #requiresConfigurationFragments(Class...)}, this method takes the * Skylark module names of fragments instead of their classes. */ public Builder requiresConfigurationFragmentsBySkylarkModuleName( Collection configurationFragmentNames) { // This method is unconditionally called from Skylark code, so only consider the user to have // specified a configuration policy if the collection actually has anything in it. // TODO(mstaib): Stop caring about this as soon as all aspects have configuration policies. hasConfigurationFragmentPolicy = hasConfigurationFragmentPolicy || !configurationFragmentNames.isEmpty(); configurationFragmentPolicy .requiresConfigurationFragmentsBySkylarkModuleName(configurationFragmentNames); return this; } /** * Declares the configuration fragments that are required by this rule for the host * configuration. * *

In contrast to {@link #requiresHostConfigurationFragments(Class...)}, this method takes * the Skylark module names of fragments instead of their classes. */ public Builder requiresHostConfigurationFragmentsBySkylarkModuleName( Collection configurationFragmentNames) { // This method is unconditionally called from Skylark code, so only consider the user to have // specified a configuration policy if the collection actually has anything in it. // TODO(mstaib): Stop caring about this as soon as all aspects have configuration policies. hasConfigurationFragmentPolicy = hasConfigurationFragmentPolicy || !configurationFragmentNames.isEmpty(); configurationFragmentPolicy .requiresHostConfigurationFragmentsBySkylarkModuleName(configurationFragmentNames); return this; } /** * Sets the policy for the case where the configuration is missing required fragments (see * {@link #requiresConfigurationFragments}). */ public Builder setMissingFragmentPolicy(MissingFragmentPolicy missingFragmentPolicy) { hasConfigurationFragmentPolicy = true; configurationFragmentPolicy.setMissingFragmentPolicy(missingFragmentPolicy); return this; } /** * Builds the aspect definition. * *

The builder object is reusable afterwards. */ public AspectDefinition build() { return new AspectDefinition(name, ImmutableSet.copyOf(requiredProviders), ImmutableMap.copyOf(attributes), ImmutableSetMultimap.copyOf(attributeAspects), hasConfigurationFragmentPolicy ? configurationFragmentPolicy.build() : null); } } }