// 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 static com.google.common.collect.Sets.newEnumSet; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition; import com.google.devtools.build.lib.analysis.config.transitions.NoTransition; import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassNamePredicate; import com.google.devtools.build.lib.syntax.ClassObject; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.syntax.Type.ConversionException; import com.google.devtools.build.lib.syntax.Type.LabelClass; import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.FileTypeSet; import com.google.devtools.build.lib.util.StringUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** * Metadata of a rule attribute. Contains the attribute name and type, and an * default value to be used if none is provided in a rule declaration in a BUILD * file. Attributes are immutable, and may be shared by more than one rule (for * example, foo_binary and foo_library may share many * attributes in common). */ @Immutable public final class Attribute implements Comparable { public static final RuleClassNamePredicate ANY_RULE = RuleClassNamePredicate.unspecified(); public static final RuleClassNamePredicate NO_RULE = RuleClassNamePredicate.only(); /** * Wraps the information necessary to construct an Aspect. */ private abstract static class RuleAspect { protected final C aspectClass; protected final Function parametersExtractor; protected RuleAspect(C aspectClass, Function parametersExtractor) { this.aspectClass = aspectClass; this.parametersExtractor = parametersExtractor; } public String getName() { return this.aspectClass.getName(); } public ImmutableSet getRequiredParameters() { return ImmutableSet.of(); } public abstract Aspect getAspect(Rule rule); public C getAspectClass() { return aspectClass; } } private static class NativeRuleAspect extends RuleAspect { public NativeRuleAspect(NativeAspectClass aspectClass, Function parametersExtractor) { super(aspectClass, parametersExtractor); } @Override public Aspect getAspect(Rule rule) { AspectParameters params = parametersExtractor.apply(rule); return params == null ? null : Aspect.forNative(aspectClass, params); } } private static class SkylarkRuleAspect extends RuleAspect { private final SkylarkDefinedAspect aspect; public SkylarkRuleAspect(SkylarkDefinedAspect aspect) { super(aspect.getAspectClass(), aspect.getDefaultParametersExtractor()); this.aspect = aspect; } @Override public ImmutableSet getRequiredParameters() { return aspect.getParamAttributes(); } @Override public Aspect getAspect(Rule rule) { AspectParameters parameters = parametersExtractor.apply(rule); return Aspect.forSkylark(aspectClass, aspect.getDefinition(parameters), parameters); } } /** A RuleAspect that just wraps a pre-existing Aspect that doesn't vary with the Rule. */ private static class PredefinedRuleAspect extends RuleAspect { private final Aspect aspect; public PredefinedRuleAspect(Aspect aspect) { super(aspect.getAspectClass(), null); this.aspect = aspect; } @Override public Aspect getAspect(Rule rule) { return aspect; } } private enum PropertyFlag { MANDATORY, EXECUTABLE, UNDOCUMENTED, TAGGABLE, /** * Whether the list attribute is order-independent and can be sorted. */ ORDER_INDEPENDENT, /** * Whether the allowedRuleClassesForLabels or allowedFileTypesForLabels are * set to custom values. If so, and the attribute is called "deps", the * legacy deps checking is skipped, and the new stricter checks are used * instead. For non-"deps" attributes, this allows skipping the check if it * would pass anyway, as the default setting allows any rule classes and * file types. */ STRICT_LABEL_CHECKING, /** * Set for things that would cause the a compile or lint-like action to * be executed when the input changes. Used by compile_one_dependency. * Set for attributes like hdrs and srcs on cc_ rules or srcs on java_ * or py_rules. Generally not set on data/resource attributes. */ DIRECT_COMPILE_TIME_INPUT, /** * Whether the value of the list type attribute must not be an empty list. */ NON_EMPTY, /** * Verifies that the referenced rule produces a single artifact. Note that this check happens * on a per label basis, i.e. the check happens separately for every label in a label list. */ SINGLE_ARTIFACT, /** * Whether we perform silent ruleclass filtering of the dependencies of the label type * attribute according to their rule classes. I.e. elements of the list which don't match the * allowedRuleClasses predicate or not rules will be filtered out without throwing any errors. * This flag is introduced to handle plugins, do not use it in other cases. */ SILENT_RULECLASS_FILTER, // TODO(bazel-team): This is a hack introduced because of the bad design of the original rules. // Depot cleanup would be too expensive, but don't migrate this to Skylark. /** * Whether to perform analysis time filetype check on this label-type attribute or not. * If the flag is set, we skip the check that applies the allowedFileTypes filter * to generated files. Do not use this if avoidable. */ SKIP_ANALYSIS_TIME_FILETYPE_CHECK, /** * Whether the value of the attribute should come from a given set of values. */ CHECK_ALLOWED_VALUES, /** * Whether this attribute is opted out of "configurability", i.e. the ability to determine * its value based on properties of the build configuration. */ NONCONFIGURABLE, /** * Whether we should skip dependency validation checks done by * {@link com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.PrerequisiteValidator} * (for visibility, etc.). */ SKIP_PREREQ_VALIDATOR_CHECKS, /** * Whether we should check constraints on this attribute even if default enforcement policy * would skip it. See * {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics} for more on * constraints. */ CHECK_CONSTRAINTS_OVERRIDE, /** * Whether we should skip constraints checking on this attribute even if default enforcement * policy would check it. */ SKIP_CONSTRAINTS_OVERRIDE, /** * Whether we should use output_licenses to check the licences on this attribute. */ OUTPUT_LICENSES, } // TODO(bazel-team): modify this interface to extend Predicate and have an extra error // message function like AllowedValues does /** * A predicate-like class that determines whether an edge between two rules is valid or not. */ public interface ValidityPredicate { /** * This method should return null if the edge is valid, or a suitable error message * if it is not. Note that warnings are not supported. */ String checkValid(Rule from, Rule to); } public static final ValidityPredicate ANY_EDGE = new ValidityPredicate() { @Override public String checkValid(Rule from, Rule to) { return null; } }; /** * Provides a {@link SplitTransition} given the originating target {@link Rule}. The split * transition may be constant for all instances of the originating rule, or it may differ * based on attributes of that rule. For instance, a split transition on a rule's deps may differ * depending on the 'platform' attribute of the rule. */ public interface SplitTransitionProvider { /** * Returns the {@link SplitTransition} given the attribute mapper of the originating rule. */ SplitTransition apply(AttributeMap attributeMap); } /** * Implementation of {@link SplitTransitionProvider} that returns a single {@link SplitTransition} * regardless of the originating rule. */ private static class BasicSplitTransitionProvider implements SplitTransitionProvider { private final SplitTransition splitTransition; BasicSplitTransitionProvider(SplitTransition splitTransition) { this.splitTransition = splitTransition; } @Override public SplitTransition apply(AttributeMap attributeMap) { return splitTransition; } } /** * A predicate class to check if the value of the attribute comes from a predefined set. */ public static class AllowedValueSet implements PredicateWithMessage { private final Set allowedValues; public AllowedValueSet(T... values) { this(Arrays.asList(values)); } public AllowedValueSet(Iterable values) { Preconditions.checkNotNull(values); Preconditions.checkArgument(!Iterables.isEmpty(values)); allowedValues = ImmutableSet.copyOf(values); } @Override public boolean apply(Object input) { return allowedValues.contains(input); } @Override public String getErrorReason(Object value) { return String.format("has to be one of %s instead of '%s'", StringUtil.joinEnglishList(allowedValues, "or", "'"), value); } @VisibleForTesting public Collection getAllowedValues() { return allowedValues; } } public ImmutableMap> getRequiredAspectParameters() { ImmutableMap.Builder> paramBuilder = ImmutableMap.builder(); for (RuleAspect aspect : aspects) { paramBuilder.put(aspect.getName(), aspect.getRequiredParameters()); } return paramBuilder.build(); } /** * Creates a new attribute builder. * * @param name attribute name * @param type attribute type * @return attribute builder * * @param attribute type class */ public static Attribute.Builder attr(String name, Type type) { return new Builder<>(name, type); } /** * A fluent builder for the {@code Attribute} instances. * *

All methods could be called only once per builder. The attribute * already undocumented based on its name cannot be marked as undocumented. */ public static class Builder { private final String name; private final Type type; private ConfigurationTransition configTransition = NoTransition.INSTANCE; private RuleClassNamePredicate allowedRuleClassesForLabels = ANY_RULE; private RuleClassNamePredicate allowedRuleClassesForLabelsWarning = NO_RULE; private SplitTransitionProvider splitTransitionProvider; private FileTypeSet allowedFileTypesForLabels; private ValidityPredicate validityPredicate = ANY_EDGE; private Object value; private AttributeValueSource valueSource = AttributeValueSource.DIRECT; private boolean valueSet; private Predicate condition; private Set propertyFlags = EnumSet.noneOf(PropertyFlag.class); private PredicateWithMessage allowedValues = null; private RequiredProviders.Builder requiredProvidersBuilder = RequiredProviders.acceptAnyBuilder(); private HashMap> aspects = new LinkedHashMap<>(); /** * Creates an attribute builder with given name and type. This attribute is optional, uses * target configuration and has a default value the same as its type default value. This * attribute will be marked as undocumented if its name starts with the dollar sign ({@code $}) * or colon ({@code :}). * * @param name attribute name * @param type attribute type */ public Builder(String name, Type type) { this.name = Preconditions.checkNotNull(name); this.type = Preconditions.checkNotNull(type); if (isImplicit(name) || isLateBound(name)) { setPropertyFlag(PropertyFlag.UNDOCUMENTED, "undocumented"); } } private Builder setPropertyFlag(PropertyFlag flag, String propertyName) { Preconditions.checkState( !propertyFlags.contains(flag), "%s flag is already set", propertyName); propertyFlags.add(flag); return this; } /** * Sets the property flag of the corresponding name if exists, otherwise throws an Exception. * Only meant to use from Skylark, do not use from Java. */ public Builder setPropertyFlag(String propertyName) { PropertyFlag flag = null; try { flag = PropertyFlag.valueOf(propertyName); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("unknown attribute flag " + propertyName); } setPropertyFlag(flag, propertyName); return this; } /** * Makes the built attribute mandatory. */ public Builder mandatory() { return setPropertyFlag(PropertyFlag.MANDATORY, "mandatory"); } /** * Makes the built attribute non empty, meaning the attribute cannot have an empty list value. * Only applicable for list type attributes. */ public Builder nonEmpty() { Preconditions.checkNotNull(type.getListElementType(), "attribute '%s' must be a list", name); return setPropertyFlag(PropertyFlag.NON_EMPTY, "non_empty"); } /** * Makes the built attribute producing a single artifact. */ public Builder singleArtifact() { Preconditions.checkState(type.getLabelClass() == LabelClass.DEPENDENCY, "attribute '%s' must be a label-valued type", name); return setPropertyFlag(PropertyFlag.SINGLE_ARTIFACT, "single_artifact"); } /** * Forces silent ruleclass filtering on the label type attribute. * This flag is introduced to handle plugins, do not use it in other cases. */ public Builder silentRuleClassFilter() { Preconditions.checkState(type.getLabelClass() == LabelClass.DEPENDENCY, "must be a label-valued type"); return setPropertyFlag(PropertyFlag.SILENT_RULECLASS_FILTER, "silent_ruleclass_filter"); } /** * Skip analysis time filetype check. Don't use it if avoidable. */ public Builder skipAnalysisTimeFileTypeCheck() { Preconditions.checkState(type.getLabelClass() == LabelClass.DEPENDENCY, "must be a label-valued type"); return setPropertyFlag(PropertyFlag.SKIP_ANALYSIS_TIME_FILETYPE_CHECK, "skip_analysis_time_filetype_check"); } /** * Mark the built attribute as order-independent. */ public Builder orderIndependent() { Preconditions.checkNotNull(type.getListElementType(), "attribute '%s' must be a list", name); return setPropertyFlag(PropertyFlag.ORDER_INDEPENDENT, "order-independent"); } /** * Mark the built attribute as to use output_licenses for license checking. */ public Builder useOutputLicenses() { Preconditions.checkState(BuildType.isLabelType(type), "must be a label type"); return setPropertyFlag(PropertyFlag.OUTPUT_LICENSES, "output_license"); } /** * Defines the configuration transition for this attribute. */ public Builder cfg(SplitTransitionProvider splitTransitionProvider) { Preconditions.checkState(this.configTransition == NoTransition.INSTANCE, "the configuration transition is already set"); this.splitTransitionProvider = Preconditions.checkNotNull(splitTransitionProvider); return this; } /** * Defines the configuration transition for this attribute. Defaults to * {@code NONE}. */ public Builder cfg(SplitTransition configTransition) { return cfg(new BasicSplitTransitionProvider(Preconditions.checkNotNull(configTransition))); } /** * Defines the configuration transition for this attribute. Defaults to * {@code NONE}. */ public Builder cfg(ConfigurationTransition configTransition) { Preconditions.checkState(this.configTransition == NoTransition.INSTANCE, "the configuration transition is already set"); if (configTransition instanceof SplitTransition) { return cfg((SplitTransition) configTransition); } else { this.configTransition = configTransition; return this; } } /** * Requires the attribute target to be executable; only for label or label * list attributes. Defaults to {@code false}. */ public Builder exec() { return setPropertyFlag(PropertyFlag.EXECUTABLE, "executable"); } /** * Indicates that the attribute (like srcs or hdrs) should be used as an input when calculating * compile_one_dependency. */ public Builder direct_compile_time_input() { return setPropertyFlag(PropertyFlag.DIRECT_COMPILE_TIME_INPUT, "direct_compile_time_input"); } /** * Makes the built attribute undocumented. * * @param reason explanation why the attribute is undocumented. This is not * used but required for documentation */ public Builder undocumented(String reason) { return setPropertyFlag(PropertyFlag.UNDOCUMENTED, "undocumented"); } /** * Sets the attribute default value. The type of the default value must * match the type parameter. (e.g. list=[], integer=0, string="", * label=null). The {@code defaultValue} must be immutable. * *

If defaultValue is of type Label and is a target, that target will * become an implicit dependency of the Rule; we will load the target * (and its dependencies) if it encounters the Rule and build the target * if needs to apply the Rule. */ public Builder value(TYPE defaultValue) { Preconditions.checkState(!valueSet, "the default value is already set"); value = defaultValue; valueSet = true; return this; } /** * See value(TYPE) above. This method is only meant for Skylark usage. * *

The parameter {@code context} is relevant iff the default value is a Label string. In this * case, {@code context} must point to the parent Label in order to be able to convert the * default value string to a proper Label. * * @param parameterName The name of the attribute to use in error messages */ public Builder defaultValue( Object defaultValue, Object context, @Nullable String parameterName) throws ConversionException { Preconditions.checkState(!valueSet, "the default value is already set"); value = type.convert( defaultValue, ((parameterName == null) ? "" : String.format("parameter '%s' of ", parameterName)) + String.format("attribute '%s'", name), context); valueSet = true; return this; } /** See value(TYPE) above. This method is only meant for Skylark usage. */ public Builder defaultValue(Object defaultValue) throws ConversionException { return defaultValue(defaultValue, null, null); } public boolean isValueSet() { return valueSet; } /** * Sets the attribute default value to a computed default value - use this when the default * value is a function of other attributes of the Rule. The type of the computed default value * for a mandatory attribute must match the type parameter: (e.g. list=[], integer=0, string="", * label=null). The {@code defaultValue} implementation must be immutable. * *

If the computed default returns a Label that is a target, that target will become an * implicit dependency of this Rule; we will load the target (and its dependencies) if it * encounters the Rule and build the target if needs to apply the Rule. */ public Builder value(ComputedDefault defaultValue) { Preconditions.checkState(!valueSet, "the default value is already set"); value = defaultValue; valueSource = AttributeValueSource.COMPUTED_DEFAULT; valueSet = true; return this; } /** * Sets the attribute default value to a Skylark computed default template. Like a native * Computed Default, this allows a Skylark-defined Rule Class to specify that the default value * of an attribute is a function of other attributes of the Rule. * *

During the loading phase, the computed default template will be specialized for each rule * it applies to. Those rules' attribute values will not be references to {@link * SkylarkComputedDefaultTemplate}s, but instead will be references to {@link * SkylarkComputedDefault}s. * *

If the computed default returns a Label that is a target, that target will become an * implicit dependency of this Rule; we will load the target (and its dependencies) if it * encounters the Rule and build the target if needs to apply the Rule. */ public Builder value(SkylarkComputedDefaultTemplate skylarkComputedDefaultTemplate) { Preconditions.checkState(!valueSet, "the default value is already set"); value = skylarkComputedDefaultTemplate; valueSource = AttributeValueSource.COMPUTED_DEFAULT; valueSet = true; return this; } /** * Sets the attribute default value to be late-bound, i.e., it is derived from the build * configuration and/or the rule's configured attributes. */ public Builder value(LateBoundDefault defaultValue) { Preconditions.checkState(!valueSet, "the default value is already set"); value = defaultValue; valueSource = AttributeValueSource.LATE_BOUND; valueSet = true; return this; } /** * Returns where the value of this attribute comes from. Useful only for Skylark. */ public AttributeValueSource getValueSource() { return valueSource; } /** * Sets a condition predicate. The default value of the attribute only applies if the condition * evaluates to true. If the value is explicitly provided, then this condition is ignored. * *

The condition is only evaluated if the attribute is not explicitly set, and after all * explicit attributes have been set. It can generally not access default values of other * attributes. */ public Builder condition(Predicate condition) { Preconditions.checkState(this.condition == null, "the condition is already set"); this.condition = condition; return this; } /** * Switches on the capability of an attribute to be published to the rule's * tag set. */ public Builder taggable() { return setPropertyFlag(PropertyFlag.TAGGABLE, "taggable"); } /** * Disables dependency checks done by * {@link com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.PrerequisiteValidator}. */ public Builder skipPrereqValidatorCheck() { return setPropertyFlag(PropertyFlag.SKIP_PREREQ_VALIDATOR_CHECKS, "skip_prereq_validator_checks"); } /** * Enforces constraint checking on this attribute even if default enforcement policy would skip * it. If default policy checks the attribute, this is a no-op. * *

Most attributes are enforced by default, so in the common case this call is unnecessary. * *

See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics#getConstraintCheckedDependencies} * for enforcement policy details. */ public Builder checkConstraints() { Verify.verify(!propertyFlags.contains(PropertyFlag.SKIP_CONSTRAINTS_OVERRIDE), "constraint checking is already overridden to be skipped"); return setPropertyFlag(PropertyFlag.CHECK_CONSTRAINTS_OVERRIDE, "check_constraints"); } /** * Skips constraint checking on this attribute even if default enforcement policy would check * it. If default policy skips the attribute, this is a no-op. * *

See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics#getConstraintCheckedDependencies} * for enforcement policy details. */ public Builder dontCheckConstraints() { Verify.verify(!propertyFlags.contains(PropertyFlag.CHECK_CONSTRAINTS_OVERRIDE), "constraint checking is already overridden to be checked"); return setPropertyFlag(PropertyFlag.SKIP_CONSTRAINTS_OVERRIDE, "dont_check_constraints"); } /** * If this is a label or label-list attribute, then this sets the allowed rule types for the * labels occurring in the attribute. * *

If the attribute contains Labels of any other rule type, then if they're in * {@link #allowedRuleClassesForLabelsWarning}, the build continues with a warning. Else if * they fulfill {@link #getMandatoryNativeProvidersList()}, the build continues without error. * Else the build fails during analysis. * *

If neither this nor {@link #allowedRuleClassesForLabelsWarning} is set, only rules that * fulfill {@link #getMandatoryNativeProvidersList()} build without error. * *

This only works on a per-target basis, not on a per-file basis; with other words, it * works for 'deps' attributes, but not 'srcs' attributes. */ public Builder allowedRuleClasses(Iterable allowedRuleClasses) { return allowedRuleClasses( RuleClassNamePredicate.only(allowedRuleClasses)); } /** * If this is a label or label-list attribute, then this sets the allowed rule types for the * labels occurring in the attribute. * *

If the attribute contains Labels of any other rule type, then if they're in * {@link #allowedRuleClassesForLabelsWarning}, the build continues with a warning. Else if * they fulfill {@link #getMandatoryNativeProvidersList()}, the build continues without error. * Else the build fails during analysis. * *

If neither this nor {@link #allowedRuleClassesForLabelsWarning} is set, only rules that * fulfill {@link #getMandatoryNativeProvidersList()} build without error. * *

This only works on a per-target basis, not on a per-file basis; with other words, it * works for 'deps' attributes, but not 'srcs' attributes. */ public Builder allowedRuleClasses(RuleClassNamePredicate allowedRuleClasses) { Preconditions.checkState(type.getLabelClass() == LabelClass.DEPENDENCY, "must be a label-valued type"); propertyFlags.add(PropertyFlag.STRICT_LABEL_CHECKING); allowedRuleClassesForLabels = allowedRuleClasses; return this; } /** * If this is a label or label-list attribute, then this sets the allowed rule types for the * labels occurring in the attribute. * *

If the attribute contains Labels of any other rule type, then if they're in * {@link #allowedRuleClassesForLabelsWarning}, the build continues with a warning. Else if * they fulfill {@link #getMandatoryNativeProvidersList()}, the build continues without error. * Else the build fails during analysis. * *

If neither this nor {@link #allowedRuleClassesForLabelsWarning} is set, only rules that * fulfill {@link #getMandatoryNativeProvidersList()} build without error. * *

This only works on a per-target basis, not on a per-file basis; with other words, it * works for 'deps' attributes, but not 'srcs' attributes. */ public Builder allowedRuleClasses(String... allowedRuleClasses) { return allowedRuleClasses(ImmutableSet.copyOf(allowedRuleClasses)); } /** * If this is a label or label-list attribute, then this sets the allowed * file types for file labels occurring in the attribute. If the attribute * contains labels that correspond to files of any other type, then an error * is produced during the analysis phase. * *

This only works on a per-target basis, not on a per-file basis; with * other words, it works for 'deps' attributes, but not 'srcs' attributes. */ public Builder allowedFileTypes(FileTypeSet allowedFileTypes) { Preconditions.checkState(type.getLabelClass() == LabelClass.DEPENDENCY, "must be a label-valued type"); propertyFlags.add(PropertyFlag.STRICT_LABEL_CHECKING); allowedFileTypesForLabels = Preconditions.checkNotNull(allowedFileTypes); return this; } /** * Allow all files for legacy compatibility. All uses of this method should be audited and then * removed. In some cases, it's correct to allow any file, but mostly the set of files should be * restricted to a reasonable set. */ public Builder legacyAllowAnyFileType() { return allowedFileTypes(FileTypeSet.ANY_FILE); } /** * If this is a label or label-list attribute, then this sets the allowed * file types for file labels occurring in the attribute. If the attribute * contains labels that correspond to files of any other type, then an error * is produced during the analysis phase. * *

This only works on a per-target basis, not on a per-file basis; with * other words, it works for 'deps' attributes, but not 'srcs' attributes. */ public Builder allowedFileTypes(FileType... allowedFileTypes) { return allowedFileTypes(FileTypeSet.of(allowedFileTypes)); } /** * If this is a label or label-list attribute, then this sets the allowed rule types with * warning for the labels occurring in the attribute. This must be a disjoint set from * {@link #allowedRuleClasses}. * *

If the attribute contains Labels of any other rule type (other than this or those set in * allowedRuleClasses()) and they fulfill {@link #getMandatoryNativeProvidersList()}}, the build * continues without error. Else the build fails during analysis. * *

If neither this nor {@link #allowedRuleClassesForLabels} is set, only rules that * fulfill {@link #getMandatoryNativeProvidersList()} build without error. * *

This only works on a per-target basis, not on a per-file basis; with other words, it * works for 'deps' attributes, but not 'srcs' attributes. */ public Builder allowedRuleClassesWithWarning(Collection allowedRuleClasses) { return allowedRuleClassesWithWarning( RuleClassNamePredicate.only(allowedRuleClasses)); } /** * If this is a label or label-list attribute, then this sets the allowed rule types with * warning for the labels occurring in the attribute. This must be a disjoint set from * {@link #allowedRuleClasses}. * *

If the attribute contains Labels of any other rule type (other than this or those set in * allowedRuleClasses()) and they fulfill {@link #getMandatoryNativeProvidersList()}}, the build * continues without error. Else the build fails during analysis. * *

If neither this nor {@link #allowedRuleClassesForLabels} is set, only rules that * fulfill {@link #getMandatoryNativeProvidersList()} build without error. * *

This only works on a per-target basis, not on a per-file basis; with other words, it * works for 'deps' attributes, but not 'srcs' attributes. */ public Builder allowedRuleClassesWithWarning(RuleClassNamePredicate allowedRuleClasses) { Preconditions.checkState(type.getLabelClass() == LabelClass.DEPENDENCY, "must be a label-valued type"); propertyFlags.add(PropertyFlag.STRICT_LABEL_CHECKING); allowedRuleClassesForLabelsWarning = allowedRuleClasses; return this; } /** * If this is a label or label-list attribute, then this sets the allowed rule types with * warning for the labels occurring in the attribute. This must be a disjoint set from {@link * #allowedRuleClasses}. * *

If the attribute contains Labels of any other rule type (other than this or those set in * allowedRuleClasses()) and they fulfill {@link #getRequiredProviders()}}, the build continues * without error. Else the build fails during analysis. * *

If neither this nor {@link #allowedRuleClassesForLabels} is set, only rules that fulfill * {@link #getRequiredProviders()} build without error. * *

This only works on a per-target basis, not on a per-file basis; with other words, it works * for 'deps' attributes, but not 'srcs' attributes. */ public Builder allowedRuleClassesWithWarning(String... allowedRuleClasses) { return allowedRuleClassesWithWarning(ImmutableSet.copyOf(allowedRuleClasses)); } /** * Sets a list of lists of mandatory native providers. Every configured target occurring in this * label type attribute has to provide all the providers from one of those lists, otherwise an * error is produced during the analysis phase. */ public final Builder mandatoryNativeProvidersList( Iterable>> providersList) { Preconditions.checkState(type.getLabelClass() == LabelClass.DEPENDENCY, "must be a label-valued type"); for (Iterable> providers : providersList) { this.requiredProvidersBuilder.addNativeSet(ImmutableSet.copyOf(providers)); } return this; } public Builder mandatoryNativeProviders( Iterable> providers) { if (providers.iterator().hasNext()) { mandatoryNativeProvidersList(ImmutableList.of(providers)); } return this; } /** * Sets a list of sets of mandatory Skylark providers. Every configured target occurring in * this label type attribute has to provide all the providers from one of those sets, * or be one of {@link #allowedRuleClasses}, otherwise an error is produced during * the analysis phase. */ public Builder mandatoryProvidersList( Iterable> providersList){ Preconditions.checkState(type.getLabelClass() == LabelClass.DEPENDENCY, "must be a label-valued type"); for (Iterable providers : providersList) { this.requiredProvidersBuilder.addSkylarkSet(ImmutableSet.copyOf(providers)); } return this; } public Builder legacyMandatoryProviders(String... ids) { return mandatoryProviders( Iterables.transform( Arrays.asList(ids), s -> { Preconditions.checkNotNull(s); return SkylarkProviderIdentifier.forLegacy(s); })); } public Builder mandatoryProviders(Iterable providers) { if (providers.iterator().hasNext()) { mandatoryProvidersList(ImmutableList.of(providers)); } return this; } public Builder mandatoryProviders(SkylarkProviderIdentifier... providers) { mandatoryProviders(Arrays.asList(providers)); return this; } /** * Asserts that a particular parameterized aspect probably needs to be computed for all direct * dependencies through this attribute. * * @param evaluator function that extracts aspect parameters from rule. If it returns null, * then the aspect will not be attached. */ public Builder aspect( NativeAspectClass aspect, Function evaluator) { NativeRuleAspect nativeRuleAspect = new NativeRuleAspect(aspect, evaluator); RuleAspect oldAspect = this.aspects.put(nativeRuleAspect.getName(), nativeRuleAspect); if (oldAspect != null) { throw new AssertionError( String.format("Aspect %s has already been added", oldAspect.getName())); } return this; } /** * Asserts that a particular parameterized aspect probably needs to be computed for all direct * dependencies through this attribute. */ public Builder aspect(NativeAspectClass aspect) { return this.aspect(aspect, input -> AspectParameters.EMPTY); } public Builder aspect(SkylarkDefinedAspect skylarkAspect, Location location) throws EvalException { SkylarkRuleAspect skylarkRuleAspect = new SkylarkRuleAspect(skylarkAspect); RuleAspect oldAspect = this.aspects.put(skylarkAspect.getName(), skylarkRuleAspect); if (oldAspect != null) { throw new EvalException( location, String.format("aspect %s added more than once", skylarkAspect.getName())); } return this; } /** * Should only be used for deserialization. */ public Builder aspect(final Aspect aspect) { PredefinedRuleAspect predefinedRuleAspect = new PredefinedRuleAspect(aspect); RuleAspect oldAspect = this.aspects.put(predefinedRuleAspect.getName(), predefinedRuleAspect); if (oldAspect != null) { throw new AssertionError( String.format("Aspect %s has already been added", oldAspect.getName())); } return this; } /** * Sets the predicate-like edge validity checker. */ public Builder validityPredicate(ValidityPredicate validityPredicate) { propertyFlags.add(PropertyFlag.STRICT_LABEL_CHECKING); this.validityPredicate = validityPredicate; return this; } /** * The value of the attribute must be one of allowedValues. */ public Builder allowedValues(PredicateWithMessage allowedValues) { this.allowedValues = allowedValues; propertyFlags.add(PropertyFlag.CHECK_ALLOWED_VALUES); return this; } /** * Makes the built attribute "non-configurable", i.e. its value cannot be influenced by * the build configuration. Attributes are "configurable" unless explicitly opted out here. * *

Non-configurability indicates an exceptional state: there exists Blaze logic that needs * the attribute's value, has no access to configurations, and can't apply a workaround * through an appropriate {@link AbstractAttributeMapper} implementation. Scenarios like * this should be as uncommon as possible, so it's important we maintain clear documentation * on what causes them and why users consequently can't configure certain attributes. * * @param reason why this attribute can't be configurable. This isn't used by Blaze - it's * solely a documentation mechanism. */ public Builder nonconfigurable(String reason) { Preconditions.checkState(!reason.isEmpty()); return setPropertyFlag(PropertyFlag.NONCONFIGURABLE, "nonconfigurable"); } /** * Creates the attribute. Uses name, type, optionality, configuration type * and the default value configured by the builder. */ public Attribute build() { return build(this.name); } /** * Creates the attribute. Uses type, optionality, configuration type * and the default value configured by the builder. Use the name * passed as an argument. This function is used by Skylark where the * name is provided only when we build. We don't want to modify the * builder, as it is shared in a multithreaded environment. */ public Attribute build(String name) { Preconditions.checkState(!name.isEmpty(), "name has not been set"); if (valueSource == AttributeValueSource.LATE_BOUND) { Preconditions.checkState(isLateBound(name)); } // TODO(bazel-team): Set the default to be no file type, then remove this check, and also // remove all allowedFileTypes() calls without parameters. // do not modify this.allowedFileTypesForLabels, instead create a copy. FileTypeSet allowedFileTypesForLabels = this.allowedFileTypesForLabels; if (type.getLabelClass() == LabelClass.DEPENDENCY) { if (isPrivateAttribute(name) && allowedFileTypesForLabels == null) { allowedFileTypesForLabels = FileTypeSet.ANY_FILE; } Preconditions.checkNotNull( allowedFileTypesForLabels, "allowedFileTypesForLabels not set for %s", name); } else if (type.getLabelClass() == LabelClass.OUTPUT) { // TODO(bazel-team): Set the default to no file type and make explicit calls instead. if (allowedFileTypesForLabels == null) { allowedFileTypesForLabels = FileTypeSet.ANY_FILE; } } Preconditions.checkState( !allowedRuleClassesForLabels.consideredOverlapping(allowedRuleClassesForLabelsWarning), "allowedRuleClasses %s and allowedRuleClassesWithWarning %s " + "may not contain the same rule classes", allowedRuleClassesForLabels, allowedRuleClassesForLabelsWarning); return new Attribute( name, type, Sets.immutableEnumSet(propertyFlags), valueSet ? value : type.getDefaultValue(), configTransition, splitTransitionProvider, allowedRuleClassesForLabels, allowedRuleClassesForLabelsWarning, allowedFileTypesForLabels, validityPredicate, condition, allowedValues, requiredProvidersBuilder.build(), ImmutableList.copyOf(aspects.values())); } } /** * A strategy for dealing with too many computations, used when creating lookup tables for {@link * ComputedDefault}s. * * @param The type of exception this strategy throws if too many computations are * attempted. */ interface ComputationLimiter { void onComputationCount(int count) throws TException; } /** * An implementation of {@link ComputationLimiter} that never throws. For use with * natively-defined {@link ComputedDefault}s, which are limited in the number of configurable * attributes they depend on, not on the number of different combinations of possible inputs. */ private static final ComputationLimiter NULL_COMPUTATION_LIMITER = new ComputationLimiter() { @Override public void onComputationCount(int count) throws RuntimeException {} }; /** Exception for computed default attributes that depend on too many configurable attributes. */ private static class TooManyConfigurableAttributesException extends Exception { TooManyConfigurableAttributesException(int max) { super( String.format( "Too many configurable attributes to compute all possible values: " + "Found more than %d possible values.", max)); } } private static class FixedComputationLimiter implements ComputationLimiter { /** Upper bound of the number of combinations of values for a computed default attribute. */ private static final int COMPUTED_DEFAULT_MAX_COMBINATIONS = 64; private static final FixedComputationLimiter INSTANCE = new FixedComputationLimiter(); @Override public void onComputationCount(int count) throws TooManyConfigurableAttributesException { if (count > COMPUTED_DEFAULT_MAX_COMBINATIONS) { throw new TooManyConfigurableAttributesException(COMPUTED_DEFAULT_MAX_COMBINATIONS); } } } /** * Specifies how values of {@link ComputedDefault} attributes are computed based on the values of * other attributes. * *

The {@code TComputeException} type parameter allows the two specializations of this class to * describe whether and how their computations throw. For natively defined computed defaults, * computation does not throw, but for Skylark-defined computed defaults, computation may throw * {@link InterruptedException}. */ private abstract static class ComputationStrategy { abstract Object compute(AttributeMap map) throws TComputeException; /** * Returns a lookup table mapping from: * *

    *
  • tuples of values that may be assigned by {@code rule} to attributes with names in {@code * dependencies} (note that there may be more than one such tuple for any given rule, if any * of the dependencies are configurable) *
* *

to: * *

    *
  • the value {@link #compute(AttributeMap)} evaluates to when the provided {@link * AttributeMap} contains the values specified by that assignment, or {@code null} if the * {@link ComputationStrategy} failed to evaluate. *
* *

The lookup table contains a tuple for each possible assignment to the {@code dependencies} * attributes. The meaning of each tuple is well-defined because {@code dependencies} is * ordered. * *

This is useful because configurable attributes may have many possible values. During the * loading phase a configurable attribute can't be resolved to a single value. Configuration * information, needed to resolve such an attribute, is only available during analysis. However, * any labels that a ComputedDefault attribute may evaluate to must be loaded during the loading * phase. */ Map, T> computeValuesForAllCombinations( List dependencies, Type type, Rule rule, ComputationLimiter limiter) throws TComputeException, TLimitException { // This will hold every (value1, value2, ..) combination of the declared dependencies. // Collect those combinations. AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule); List> depMaps = mapper.visitAttributes(dependencies, limiter); // For each combination, call compute() on a specialized AttributeMap providing those // values. Map, T> valueMap = new HashMap<>(depMaps.size()); for (Map depMap : depMaps) { AttributeMap attrMap = mapper.createMapBackedAttributeMap(depMap); Object value = compute(attrMap); List key = createDependencyAssignmentTuple(dependencies, attrMap); valueMap.put(key, type.cast(value)); } return valueMap; } /** * Given an {@link AttributeMap}, containing an assignment to each attribute in {@code * dependencies}, this returns a list of the assigned values, ordered as {@code dependencies} is * ordered. */ static List createDependencyAssignmentTuple( List dependencies, AttributeMap attrMap) { ArrayList tuple = new ArrayList<>(dependencies.size()); for (String attrName : dependencies) { Type attrType = attrMap.getAttributeType(attrName); tuple.add(attrMap.get(attrName, attrType)); } return tuple; } } /** * A computed default is a default value for a Rule attribute that is a function of other * attributes of the rule. * *

Attributes whose defaults are computed are first initialized to the default for their type, * and then the computed defaults are evaluated after all non-computed defaults have been * initialized. There is no defined order among computed defaults, so they must not depend on each * other. * *

If a computed default reads the value of another attribute, at least one of the following * must be true: * *

    *
  1. The other attribute must be declared in the computed default's constructor *
  2. The other attribute must be non-configurable ({@link Builder#nonconfigurable} *
* *

The reason for enforced declarations is that, since attribute values might be configurable, * a computed default that depends on them may itself take multiple values. Since we have no * access to a target's configuration at the time these values are computed, we need the ability * to probe the default's *complete* dependency space. Declared dependencies allow us to do so * sanely. Non-configurable attributes don't have this problem because their value is fixed and * known even without configuration information. * *

Implementations of this interface must be immutable. */ public abstract static class ComputedDefault { private final ImmutableList dependencies; /** * Create a computed default that can read all non-configurable attribute values and no * configurable attribute values. */ public ComputedDefault() { this(ImmutableList.of()); } /** * Create a computed default that can read all non-configurable attributes values and one * explicitly specified configurable attribute value */ public ComputedDefault(String depAttribute) { this(ImmutableList.of(depAttribute)); } /** * Create a computed default that can read all non-configurable attributes values and two * explicitly specified configurable attribute values. */ public ComputedDefault(String depAttribute1, String depAttribute2) { this(ImmutableList.of(depAttribute1, depAttribute2)); } /** * Creates a computed default that can read all non-configurable attributes and some explicitly * specified configurable attribute values. * *

This constructor should not be used by native {@link ComputedDefault} functions. The limit * of at-most-two depended-on configurable attributes is intended, to limit the exponential * growth of possible values. {@link SkylarkComputedDefault} uses this, but is limited by {@link * FixedComputationLimiter#COMPUTED_DEFAULT_MAX_COMBINATIONS}. */ protected ComputedDefault(ImmutableList dependencies) { // Order is important for #createDependencyAssignmentTuple. this.dependencies = Ordering.natural().immutableSortedCopy(dependencies); } Iterable getPossibleValues(Type type, Rule rule) { final ComputedDefault owner = ComputedDefault.this; ComputationStrategy strategy = new ComputationStrategy() { @Override public Object compute(AttributeMap map) { return owner.getDefault(map); } }; // Note that this uses ArrayList instead of something like ImmutableList because some // values may be null. return new ArrayList<>( strategy .computeValuesForAllCombinations(dependencies, type, rule, NULL_COMPUTATION_LIMITER) .values()); } /** The list of configurable attributes this ComputedDefault declares it may read. */ public ImmutableList dependencies() { return dependencies; } /** * Returns the value this {@link ComputedDefault} evaluates to, given the inputs contained in * {@code rule}. */ public abstract Object getDefault(AttributeMap rule); } /** * A Skylark-defined computed default, which can be precomputed for a specific {@link Rule} by * calling {@link #computePossibleValues}, which returns a {@link SkylarkComputedDefault} that * contains a lookup table. */ public static final class SkylarkComputedDefaultTemplate { private final Type type; private final SkylarkCallbackFunction callback; private final Location location; private final ImmutableList dependencies; /** * Creates a new SkylarkComputedDefaultTemplate that allows the computation of attribute values * via a callback function during loading phase. * * @param type The type of the value of this attribute. * @param dependencies A list of all names of other attributes that are accessed by this * attribute. * @param callback A function to compute the actual attribute value. * @param location The location of the Skylark function. */ public SkylarkComputedDefaultTemplate( Type type, ImmutableList dependencies, SkylarkCallbackFunction callback, Location location) { this.type = Preconditions.checkNotNull(type); // Order is important for #createDependencyAssignmentTuple. this.dependencies = Ordering.natural().immutableSortedCopy(Preconditions.checkNotNull(dependencies)); this.callback = Preconditions.checkNotNull(callback); this.location = Preconditions.checkNotNull(location); } /** * Returns a {@link SkylarkComputedDefault} containing a lookup table specifying the output of * this {@link SkylarkComputedDefaultTemplate}'s callback given each possible assignment {@code * rule} might make to the attributes specified by {@link #dependencies}. * *

If the rule is missing an attribute specified by {@link #dependencies}, or if there are * too many possible assignments, or if any evaluation fails, this throws {@link * CannotPrecomputeDefaultsException}. * *

May only be called after all non-{@link ComputedDefault} attributes have been set on the * {@code rule}. */ SkylarkComputedDefault computePossibleValues( Attribute attr, final Rule rule, final EventHandler eventHandler) throws InterruptedException, CannotPrecomputeDefaultsException { final SkylarkComputedDefaultTemplate owner = SkylarkComputedDefaultTemplate.this; final String msg = String.format( "Cannot compute default value of attribute '%s' in rule '%s': ", attr.getPublicName(), rule.getLabel()); final AtomicReference caughtEvalExceptionIfAny = new AtomicReference<>(); ComputationStrategy strategy = new ComputationStrategy() { @Override public Object compute(AttributeMap map) throws InterruptedException { try { return owner.computeValue(eventHandler, map); } catch (EvalException ex) { caughtEvalExceptionIfAny.compareAndSet(null, ex); return null; } } }; ImmutableList.Builder> dependencyTypesBuilder = ImmutableList.builder(); Map, Object> lookupTable = new HashMap<>(); try { for (String dependency : dependencies) { Attribute attribute = rule.getRuleClassObject().getAttributeByNameMaybe(dependency); if (attribute == null) { throw new AttributeNotFoundException( String.format("No such attribute %s in rule %s", dependency, rule.getLabel())); } dependencyTypesBuilder.add(attribute.getType()); } lookupTable.putAll( strategy.computeValuesForAllCombinations( dependencies, attr.getType(), rule, FixedComputationLimiter.INSTANCE)); if (caughtEvalExceptionIfAny.get() != null) { throw caughtEvalExceptionIfAny.get(); } } catch (AttributeNotFoundException | TooManyConfigurableAttributesException | EvalException ex) { String error = msg + ex.getMessage(); rule.reportError(error, eventHandler); throw new CannotPrecomputeDefaultsException(error); } return new SkylarkComputedDefault(dependencies, dependencyTypesBuilder.build(), lookupTable); } private Object computeValue(EventHandler eventHandler, AttributeMap rule) throws EvalException, InterruptedException { Map attrValues = new HashMap<>(); for (String attrName : rule.getAttributeNames()) { Attribute attr = rule.getAttributeDefinition(attrName); if (!attr.hasComputedDefault()) { Object value = rule.get(attrName, attr.getType()); if (!EvalUtils.isNullOrNone(value)) { attrValues.put(attr.getName(), value); } } } return invokeCallback(eventHandler, attrValues); } private Object invokeCallback(EventHandler eventHandler, Map attrValues) throws EvalException, InterruptedException { ClassObject attrs = NativeProvider.STRUCT.create( attrValues, "No such regular (non computed) attribute '%s'."); Object result = callback.call(eventHandler, attrs); try { return type.cast((result == Runtime.NONE) ? type.getDefaultValue() : result); } catch (ClassCastException ex) { throw new EvalException( location, String.format( "expected '%s', but got '%s'", type, EvalUtils.getDataTypeName(result, true))); } } private static class AttributeNotFoundException extends Exception { private AttributeNotFoundException(String message) { super(message); } } static class CannotPrecomputeDefaultsException extends Exception { private CannotPrecomputeDefaultsException(String message) { super(message); } } } /** * A class for computed attributes defined in Skylark. * *

Unlike {@link ComputedDefault}, instances of this class contain a pre-computed table of all * possible assignments of depended-on attributes and what the Skylark function evaluates to, and * {@link #getPossibleValues(Type, Rule)} and {@link #getDefault(AttributeMap)} do lookups in that * table. */ static final class SkylarkComputedDefault extends ComputedDefault { private final List> dependencyTypes; private final Map, Object> lookupTable; /** * Creates a new SkylarkComputedDefault containing a lookup table. * * @param requiredAttributes A list of all names of other attributes that are accessed by this * attribute. * @param dependencyTypes A list of requiredAttributes' types. * @param lookupTable An exhaustive mapping from requiredAttributes assignments to values this * computed default evaluates to. */ SkylarkComputedDefault( ImmutableList requiredAttributes, ImmutableList> dependencyTypes, Map, Object> lookupTable) { super(Preconditions.checkNotNull(requiredAttributes)); this.dependencyTypes = Preconditions.checkNotNull(dependencyTypes); this.lookupTable = Preconditions.checkNotNull(lookupTable); } List> getDependencyTypes() { return dependencyTypes; } Map, Object> getLookupTable() { return lookupTable; } @Override public Object getDefault(AttributeMap rule) { List key = ComputationStrategy.createDependencyAssignmentTuple(dependencies(), rule); Preconditions.checkState( lookupTable.containsKey(key), "Error in rule '%s': precomputed value missing for dependencies: %s. Available keys: %s.", rule.getLabel(), Iterables.toString(key), Iterables.toString(lookupTable.keySet())); return lookupTable.get(key); } @Override Iterable getPossibleValues(Type type, Rule rule) { List result = new ArrayList<>(lookupTable.size()); for (Object obj : lookupTable.values()) { result.add(type.cast(obj)); } return result; } } private static class SimpleLateBoundDefault extends LateBoundDefault { private final Resolver resolver; private SimpleLateBoundDefault(boolean useHostConfiguration, Class fragmentClass, ValueT defaultValue, Resolver resolver) { super(useHostConfiguration, fragmentClass, defaultValue); this.resolver = resolver; } @Override public ValueT resolve(Rule rule, AttributeMap attributes, FragmentT input) { return resolver.resolve(rule, attributes, input); } } // TODO(b/65746853): Remove documentation about accepting BuildConfiguration when uses are cleaned // up. /** * Provider of values for late-bound attributes. See {@link Attribute#value(LateBoundDefault value)}. * *

Use sparingly - having different values for attributes during loading and analysis can * confuse users. * * @param The type of value that is used to compute this value. This is usually a * subclass of BuildConfiguration.Fragment. It may also be Void to receive null, or * BuildConfiguration itself to receive the entire configuration. * @param The type of value returned by this class. Must be either {@link Void}, a {@link * Label}, or a {@link List} of {@link Label} objects. */ @Immutable public abstract static class LateBoundDefault { /** * Functional interface for computing the value of a late-bound attribute. * *

Implementations of this interface must be immutable. */ @FunctionalInterface public interface Resolver { ValueT resolve(Rule rule, AttributeMap attributeMap, FragmentT input); } private final boolean useHostConfiguration; private final ValueT defaultValue; private final Class fragmentClass; /** * Creates a new LateBoundDefault which always returns the given value. * *

This is used primarily for matching names with late-bound attributes on other rules and * for testing. Use normal default values if the name does not matter. */ @VisibleForTesting public static LabelLateBoundDefault fromConstantForTesting(Label defaultValue) { return new LabelLateBoundDefault( false, Void.class, Preconditions.checkNotNull(defaultValue), (rule, attributes, unused) -> defaultValue) {}; } /** * Creates a new LateBoundDefault which always returns null. * *

This is used primarily for matching names with late-bound attributes on other rules and * for testing. Use normal default values if the name does not matter. */ @SuppressWarnings("unchecked") // bivariant implementation public static LateBoundDefault alwaysNull() { return (LateBoundDefault) AlwaysNullLateBoundDefault.INSTANCE; } protected LateBoundDefault( boolean useHostConfiguration, Class fragmentClass, ValueT defaultValue) { this.useHostConfiguration = useHostConfiguration; this.defaultValue = defaultValue; this.fragmentClass = fragmentClass; } /** * Whether to look up the label in the host configuration. This is only here for host * compilation tools - we usually need to look up labels in the target configuration. */ public final boolean useHostConfiguration() { return useHostConfiguration; } /** * Returns the input type that the attribute expects. This is almost always a configuration * fragment to be retrieved from the target's configuration (or the host configuration). * *

It may also be {@link Void} to receive null. This is rarely necessary, but can be used, * e.g., if the attribute is named to match an attribute in another rule which is late-bound. * *

It may also be BuildConfiguration to receive the entire configuration. This is deprecated, * and only necessary when the default is computed from methods of BuildConfiguration itself. */ public final Class getFragmentClass() { return fragmentClass; } /** The default value for the attribute that is set during the loading phase. */ public final ValueT getDefault() { return defaultValue; } /** * The actual value for the attribute for the analysis phase, which depends on the build * configuration. Note that configurations transitions are applied after the late-bound * attribute was evaluated. * * @param rule the rule being evaluated * @param attributes interface for retrieving the values of the rule's other attributes * @param input the configuration fragment to evaluate with */ public abstract ValueT resolve(Rule rule, AttributeMap attributes, FragmentT input); } /** * An abstract {@link LateBoundDefault} class so that {@code SkylarkLateBoundDefault} can derive * from {@link LateBoundDefault} without compromising the type-safety of the second generic * parameter to {@link LateBoundDefault}. */ public abstract static class AbstractLabelLateBoundDefault extends LateBoundDefault { protected AbstractLabelLateBoundDefault( boolean useHostConfiguration, Class fragmentClass, Label defaultValue) { super(useHostConfiguration, fragmentClass, defaultValue); } } private static class AlwaysNullLateBoundDefault extends SimpleLateBoundDefault { static final AlwaysNullLateBoundDefault INSTANCE = new AlwaysNullLateBoundDefault(); private AlwaysNullLateBoundDefault() { super(false, Void.class, null, (rule, attributes, unused) -> null); } } /** A {@link LateBoundDefault} for a {@link Label}. */ public static class LabelLateBoundDefault extends SimpleLateBoundDefault { private LabelLateBoundDefault( boolean useHostConfiguration, Class fragmentClass, Label defaultValue, Resolver resolver) { super(useHostConfiguration, fragmentClass, defaultValue, resolver); } /** * Creates a new LabelLateBoundDefault which uses the rule, its configured attributes, and a * fragment of the target configuration. * *

Note that the configuration fragment here does not take into account any transitions that * are on the attribute with this LabelLateBoundDefault as its value. The configuration will be * the same as the configuration given to the target bearing the attribute. * *

Nearly all LateBoundDefaults should use this constructor or {@link * LabelListLateBoundDefault#fromTargetConfiguration}. There are few situations where it isn't * the appropriate option. * *

If you want a late-bound dependency which is configured in the host configuration, just * use this method with {@link com.google.devtools.build.lib.analysis.config.HostTransition}. If * you also need to decide the label of the dependency with information gained from the host * configuration - and it's very unlikely that you do - you can use {@link * LabelLateBoundDefault#fromHostConfiguration} as well. * *

If you want to decide an attribute's value based on the value of its other attributes, use * a subclass of {@link ComputedDefault}. The only time you should need {@link * LabelListLateBoundDefault#fromRuleAndAttributesOnly} is if you need access to three or more * configurable attributes, or if you need to match names with a late-bound attribute on another * rule. * *

If you have a constant-valued attribute, but you need it to have the same name as an * attribute on another rule which is late-bound, use {@link #alwaysNull}. * * @param fragmentClass The fragment to receive from the target configuration. May also be * BuildConfiguration.class to receive the entire configuration (deprecated) - in this case, * you must only use methods of BuildConfiguration itself, and not use any fragments. * @param defaultValue The default {@link Label} to return at loading time, when the * configuration is not available. * @param resolver A function which will compute the actual value with the configuration. */ public static LabelLateBoundDefault fromTargetConfiguration( Class fragmentClass, Label defaultValue, Resolver resolver) { Preconditions.checkArgument( !fragmentClass.equals(Void.class), "Use fromRuleAndAttributesOnly to specify a LateBoundDefault which does not use " + "configuration."); return new LabelLateBoundDefault<>(false, fragmentClass, defaultValue, resolver); } /** * Creates a new LateBoundDefault which uses the rule, its configured attributes, and a fragment * of the host configuration. * *

This should only be necessary in very specialized cases. In almost all cases, you don't * need this method, just {@link #fromTargetConfiguration} and {@link * com.google.devtools.build.lib.analysis.config.HostTransition}. * *

This method only affects the configuration fragment passed to {@link #resolve}. You must * also use {@link com.google.devtools.build.lib.analysis.config.HostTransition}, so that the * dependency will be analyzed in the host configuration. * * @param fragmentClass The fragment to receive from the host configuration. May also be * BuildConfiguration.class to receive the entire configuration (deprecated) - in this case, * you must only use methods of BuildConfiguration itself, and not use any fragments. It is * very rare that a LateBoundDefault should need a host configuration fragment; use {@link * #fromTargetConfiguration} in most cases. * @param defaultValue The default {@link Label} to return at loading time, when the * configuration is not available. * @param resolver A function which will compute the actual value with the configuration. */ public static LabelLateBoundDefault fromHostConfiguration( Class fragmentClass, Label defaultValue, Resolver resolver) { Preconditions.checkArgument( !fragmentClass.equals(Void.class), "Use fromRuleAndAttributesOnly to specify a LateBoundDefault which does not use " + "configuration."); return new LabelLateBoundDefault<>(true, fragmentClass, defaultValue, resolver); } } /** A {@link LateBoundDefault} for a {@link List} of {@link Label} objects. */ public static class LabelListLateBoundDefault extends SimpleLateBoundDefault> { private LabelListLateBoundDefault( boolean useHostConfiguration, Class fragmentClass, Resolver> resolver) { super(useHostConfiguration, fragmentClass, ImmutableList.of(), resolver); } public static LabelListLateBoundDefault fromTargetConfiguration( Class fragmentClass, Resolver> resolver) { Preconditions.checkArgument( !fragmentClass.equals(Void.class), "Use fromRuleAndAttributesOnly to specify a LateBoundDefault which does not use " + "configuration."); return new LabelListLateBoundDefault<>(false, fragmentClass, resolver); } /** * Creates a new LabelListLateBoundDefault which uses only the rule and its configured * attributes. * *

This should only be necessary in very specialized cases. In almost all cases, you don't * need this method, just use {@link ComputedDefault}. * *

This is used primarily for computing values based on three or more configurable attributes * and/or matching names with late-bound attributes on other rules. * * @param resolver A function which will compute the actual value with the configuration. */ public static LabelListLateBoundDefault fromRuleAndAttributesOnly( Resolver> resolver) { return new LabelListLateBoundDefault<>(false, Void.class, resolver); } } private final String name; private final Type type; private final Set propertyFlags; // Exactly one of these conditions is true: // 1. defaultValue == null. // 2. defaultValue instanceof ComputedDefault && // type.isValid(defaultValue.getDefault()) // 3. defaultValue instanceof SkylarkComputedDefaultTemplate && // type.isValid(defaultValue.computePossibleValues().getDefault()) // 4. type.isValid(defaultValue). // 5. defaultValue instanceof LateBoundDefault && // type.isValid(defaultValue.getDefault(configuration)) // (We assume a hypothetical Type.isValid(Object) predicate.) private final Object defaultValue; private final ConfigurationTransition configTransition; private final SplitTransitionProvider splitTransitionProvider; /** * For label or label-list attributes, this predicate returns which rule * classes are allowed for the targets in the attribute. */ private final RuleClassNamePredicate allowedRuleClassesForLabels; /** * For label or label-list attributes, this predicate returns which rule * classes are allowed for the targets in the attribute with warning. */ private final RuleClassNamePredicate allowedRuleClassesForLabelsWarning; /** * For label or label-list attributes, this predicate returns which file * types are allowed for targets in the attribute that happen to be file * targets (rather than rules). */ private final FileTypeSet allowedFileTypesForLabels; /** * This predicate-like object checks * if the edge between two rules using this attribute is valid * in the dependency graph. Returns null if valid, otherwise an error message. */ private final ValidityPredicate validityPredicate; private final Predicate condition; private final PredicateWithMessage allowedValues; private final RequiredProviders requiredProviders; private final ImmutableList> aspects; /** * Constructs a rule attribute with the specified name, type and default value. * * @param name the name of the attribute * @param type the type of the attribute * @param defaultValue the default value to use for this attribute if none is specified in rule * declaration in the BUILD file. Must be null, or of type "type". May be an instance of * ComputedDefault, in which case its getDefault() method must return an instance of "type", * or null. Must be immutable. * @param configTransition the configuration transition for this attribute (which must be of type * LABEL, LABEL_LIST, NODEP_LABEL or NODEP_LABEL_LIST). */ private Attribute( String name, Type type, Set propertyFlags, Object defaultValue, ConfigurationTransition configTransition, SplitTransitionProvider splitTransitionProvider, RuleClassNamePredicate allowedRuleClassesForLabels, RuleClassNamePredicate allowedRuleClassesForLabelsWarning, FileTypeSet allowedFileTypesForLabels, ValidityPredicate validityPredicate, Predicate condition, PredicateWithMessage allowedValues, RequiredProviders requiredProviders, ImmutableList> aspects) { Preconditions.checkNotNull(configTransition); Preconditions.checkArgument( (configTransition == NoTransition.INSTANCE) || type.getLabelClass() == LabelClass.DEPENDENCY || type.getLabelClass() == LabelClass.NONDEP_REFERENCE, "Configuration transitions can only be specified for label or label list attributes"); Preconditions.checkArgument( isLateBound(name) == (defaultValue instanceof LateBoundDefault), "late bound attributes require a default value that is late bound (and vice versa): %s", name); if (isLateBound(name)) { LateBoundDefault lateBoundDefault = (LateBoundDefault) defaultValue; Preconditions.checkArgument(!lateBoundDefault.useHostConfiguration() || (configTransition.isHostTransition()), "a late bound default value using the host configuration must use the host transition"); } this.name = name; this.type = type; this.propertyFlags = propertyFlags; this.defaultValue = defaultValue; this.configTransition = configTransition; this.splitTransitionProvider = splitTransitionProvider; this.allowedRuleClassesForLabels = allowedRuleClassesForLabels; this.allowedRuleClassesForLabelsWarning = allowedRuleClassesForLabelsWarning; this.allowedFileTypesForLabels = allowedFileTypesForLabels; this.validityPredicate = validityPredicate; this.condition = condition; this.allowedValues = allowedValues; this.requiredProviders = requiredProviders; this.aspects = aspects; } /** * Returns the name of this attribute. */ public String getName() { return name; } /** * Returns the public name of this attribute. This is the name we use in Skylark code * and we can use it to display to the end-user. * Implicit and late-bound attributes start with '_' (instead of '$' or ':'). */ public String getPublicName() { return getSkylarkName(getName()); } /** * Returns the logical type of this attribute. (May differ from the actual * representation as a value in the build interpreter; for example, an * attribute may logically be a list of labels, but be represented as a list * of strings.) */ public Type getType() { return type; } private boolean getPropertyFlag(PropertyFlag flag) { return propertyFlags.contains(flag); } /** * Returns true if this parameter is mandatory. */ public boolean isMandatory() { return getPropertyFlag(PropertyFlag.MANDATORY); } /** * Returns true if this list parameter cannot have an empty list as a value. */ public boolean isNonEmpty() { return getPropertyFlag(PropertyFlag.NON_EMPTY); } /** * Returns true if this label parameter must produce a single artifact. */ public boolean isSingleArtifact() { return getPropertyFlag(PropertyFlag.SINGLE_ARTIFACT); } /** * Returns true if this label type parameter is checked by silent ruleclass filtering. */ public boolean isSilentRuleClassFilter() { return getPropertyFlag(PropertyFlag.SILENT_RULECLASS_FILTER); } /** * Returns true if this label type parameter skips the analysis time filetype check. */ public boolean isSkipAnalysisTimeFileTypeCheck() { return getPropertyFlag(PropertyFlag.SKIP_ANALYSIS_TIME_FILETYPE_CHECK); } /** * Returns true if this parameter is order-independent. */ public boolean isOrderIndependent() { return getPropertyFlag(PropertyFlag.ORDER_INDEPENDENT); } /** * Returns true if output_licenses should be used for checking licensing. */ public boolean useOutputLicenses() { return getPropertyFlag(PropertyFlag.OUTPUT_LICENSES); } /** * Returns the configuration transition for this attribute for label or label * list attributes. For other attributes it will always return {@code NONE}. */ public ConfigurationTransition getConfigurationTransition() { return configTransition; } /** * Returns the split configuration transition for this attribute. * * @param attributeMapper the attribute mapper of the current {@link Rule} * @return a SplitTransition object * @throws IllegalStateException if {@link #hasSplitConfigurationTransition} is not true */ public SplitTransition getSplitTransition(AttributeMap attributeMapper) { Preconditions.checkState(hasSplitConfigurationTransition()); return splitTransitionProvider.apply(attributeMapper); } /** * Returns true if this attribute transitions on a split transition. * See {@link SplitTransition}. */ public boolean hasSplitConfigurationTransition() { return (splitTransitionProvider != null); } /** * Returns whether the target is required to be executable for label or label * list attributes. For other attributes it always returns {@code false}. */ public boolean isExecutable() { return getPropertyFlag(PropertyFlag.EXECUTABLE); } /** * Returns {@code true} iff the rule is a direct input for an action. */ public boolean isDirectCompileTimeInput() { return getPropertyFlag(PropertyFlag.DIRECT_COMPILE_TIME_INPUT); } /** * Returns {@code true} iff this attribute requires documentation. */ public boolean isDocumented() { return !getPropertyFlag(PropertyFlag.UNDOCUMENTED); } /** * Returns {@code true} iff this attribute should be published to the rule's * tag set. Note that not all Type classes support tag conversion. */ public boolean isTaggable() { return getPropertyFlag(PropertyFlag.TAGGABLE); } public boolean isStrictLabelCheckingEnabled() { return getPropertyFlag(PropertyFlag.STRICT_LABEL_CHECKING); } /** * Returns true if the value of this attribute should be a part of a given set. */ public boolean checkAllowedValues() { return getPropertyFlag(PropertyFlag.CHECK_ALLOWED_VALUES); } public boolean performPrereqValidatorCheck() { return !getPropertyFlag(PropertyFlag.SKIP_PREREQ_VALIDATOR_CHECKS); } public boolean checkConstraintsOverride() { return getPropertyFlag(PropertyFlag.CHECK_CONSTRAINTS_OVERRIDE); } public boolean skipConstraintsOverride() { return getPropertyFlag(PropertyFlag.SKIP_CONSTRAINTS_OVERRIDE); } /** * Returns true if this attribute's value can be influenced by the build configuration. */ public boolean isConfigurable() { // Output types are excluded because of Rule#populateExplicitOutputFiles. return !(type.getLabelClass() == LabelClass.OUTPUT || getPropertyFlag(PropertyFlag.NONCONFIGURABLE)); } /** * Returns a predicate that evaluates to true for rule classes that are allowed labels in this * attribute. If this is not a label or label-list attribute, the returned predicate always * evaluates to true. * *

NOTE: This may return Predicates.alwaysTrue() as a sentinel meaning "do the right * thing", rather than actually allowing all rule classes in that attribute. Others parts of bazel * code check for that specific instance. */ public Predicate getAllowedRuleClassesPredicate() { return allowedRuleClassesForLabels.asPredicateOfRuleClass(); } /** * Returns a predicate that evaluates to true for rule classes that are * allowed labels in this attribute. If this is not a label or label-list * attribute, the returned predicate always evaluates to true. */ // TODO(b/69917891) Remove these methods once checkbuilddeps no longer depends on them. public Predicate getAllowedRuleClassNamesPredicate() { return allowedRuleClassesForLabels.asPredicateOfRuleClassName(); } /** * Returns a predicate that evaluates to true for rule classes that are * allowed labels in this attribute with warning. If this is not a label or label-list * attribute, the returned predicate always evaluates to true. */ public Predicate getAllowedRuleClassesWarningPredicate() { return allowedRuleClassesForLabelsWarning.asPredicateOfRuleClass(); } public RequiredProviders getRequiredProviders() { return requiredProviders; } public FileTypeSet getAllowedFileTypesPredicate() { return allowedFileTypesForLabels; } public ValidityPredicate getValidityPredicate() { return validityPredicate; } public Predicate getCondition() { return condition == null ? Predicates.alwaysTrue() : condition; } public PredicateWithMessage getAllowedValues() { return allowedValues; } /** * Returns the list of aspects required for dependencies through this attribute. */ public ImmutableList getAspects(Rule rule) { ImmutableList.Builder builder = ImmutableList.builder(); for (RuleAspect aspect : aspects) { Aspect a = aspect.getAspect(rule); if (a != null) { builder.add(a); } } return builder.build(); } public ImmutableList getAspectClasses() { ImmutableList.Builder result = ImmutableList.builder(); for (RuleAspect aspect : aspects) { result.add(aspect.getAspectClass()); } return result.build(); } /** * Returns the default value of this attribute in the context of the * specified Rule. For attributes with a computed default, i.e. {@code * hasComputedDefault()}, {@code rule} must be non-null since the result may * depend on the values of its other attributes. * *

The result may be null (although this is not a value in the build * language). * *

During population of the rule's attribute dictionary, all non-computed * defaults must be set before all computed ones. * * @param rule the rule to which this attribute belongs; non-null if * {@code hasComputedDefault()}; ignored otherwise. */ public Object getDefaultValue(Rule rule) { if (!getCondition().apply(rule == null ? null : NonconfigurableAttributeMapper.of(rule))) { return null; } else if (defaultValue instanceof LateBoundDefault) { return ((LateBoundDefault) defaultValue).getDefault(); } else { return defaultValue; } } /** * Returns the default value of this attribute, even if it has a condition, is a computed default, * or a late-bound default. */ @VisibleForTesting public Object getDefaultValueForTesting() { return defaultValue; } public LateBoundDefault getLateBoundDefault() { Preconditions.checkState(isLateBound()); return (LateBoundDefault) defaultValue; } /** * Returns true iff this attribute has a computed default or a condition. * * @see #getDefaultValue(Rule) */ boolean hasComputedDefault() { return (defaultValue instanceof ComputedDefault) || (defaultValue instanceof SkylarkComputedDefaultTemplate) || (condition != null); } /** * Returns if this attribute is an implicit dependency according to the naming policy that * designates implicit attributes. */ public boolean isImplicit() { return isImplicit(getName()); } /** * Returns if an attribute with the given name is an implicit dependency according to the * naming policy that designates implicit attributes. */ public static boolean isImplicit(String name) { return name.startsWith("$"); } /** * Returns if this attribute is late-bound according to the naming policy that designates * late-bound attributes. */ public boolean isLateBound() { return isLateBound(getName()); } /** * Returns if an attribute with the given name is late-bound according to the naming policy * that designates late-bound attributes. */ public static boolean isLateBound(String name) { return name.startsWith(":"); } /** Returns whether this attribute is considered private in Skylark. */ private static boolean isPrivateAttribute(String nativeAttrName) { return isLateBound(nativeAttrName) || isImplicit(nativeAttrName); } /** * Returns the Skylark-usable name of this attribute. * * Implicit and late-bound attributes start with '_' (instead of '$' or ':'). */ public static String getSkylarkName(String nativeAttrName) { if (isPrivateAttribute(nativeAttrName)) { return "_" + nativeAttrName.substring(1); } return nativeAttrName; } @Override public String toString() { return "Attribute(" + name + ", " + type + ")"; } @Override public int compareTo(Attribute other) { return name.compareTo(other.name); } /** * Returns a replica builder of this Attribute. */ public Attribute.Builder cloneBuilder(Type tp) { Preconditions.checkArgument(tp == this.type); Builder builder = new Builder<>(name, tp); builder.allowedFileTypesForLabels = allowedFileTypesForLabels; builder.allowedRuleClassesForLabels = allowedRuleClassesForLabels; builder.allowedRuleClassesForLabelsWarning = allowedRuleClassesForLabelsWarning; builder.requiredProvidersBuilder = requiredProviders.copyAsBuilder(); builder.validityPredicate = validityPredicate; builder.condition = condition; builder.configTransition = configTransition; builder.splitTransitionProvider = splitTransitionProvider; builder.propertyFlags = newEnumSet(propertyFlags, PropertyFlag.class); builder.value = defaultValue; builder.valueSet = false; builder.allowedValues = allowedValues; builder.aspects = new LinkedHashMap<>(); for (RuleAspect aspect : aspects) { builder.aspects.put(aspect.getName(), aspect); } return builder; } public Attribute.Builder cloneBuilder() { return cloneBuilder(this.type); } }