// 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.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.cmdline.Label; import com.google.devtools.build.lib.concurrent.ThreadSafety; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.Location; 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.Preconditions; 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.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, <code>foo_binary</code> and <code>foo_library</code> may share many * attributes in common). */ @Immutable public final class Attribute implements Comparable<Attribute> { public static final Predicate<RuleClass> ANY_RULE = Predicates.alwaysTrue(); public static final Predicate<RuleClass> NO_RULE = Predicates.alwaysFalse(); /** * Wraps the information necessary to construct an Aspect. */ private abstract static class RuleAspect<C extends AspectClass> { protected final C aspectClass; protected final Function<Rule, AspectParameters> parametersExtractor; protected RuleAspect(C aspectClass, Function<Rule, AspectParameters> parametersExtractor) { this.aspectClass = aspectClass; this.parametersExtractor = parametersExtractor; } public String getName() { return this.aspectClass.getName(); } public ImmutableSet<String> getRequiredParameters() { return ImmutableSet.<String>of(); } public abstract Aspect getAspect(Rule rule); public C getAspectClass() { return aspectClass; } } private static class NativeRuleAspect extends RuleAspect<NativeAspectClass> { public NativeRuleAspect(NativeAspectClass aspectClass, Function<Rule, AspectParameters> 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<SkylarkAspectClass> { private final SkylarkAspect aspect; public SkylarkRuleAspect(SkylarkAspect aspect) { super(aspect.getAspectClass(), aspect.getDefaultParametersExtractor()); this.aspect = aspect; } @Override public ImmutableSet<String> 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<AspectClass> { private final Aspect aspect; public PredefinedRuleAspect(Aspect aspect) { super(aspect.getAspectClass(), null); this.aspect = aspect; } @Override public Aspect getAspect(Rule rule) { return aspect; } } /** * A configuration transition. */ public interface Transition { /** * Usually, a non-existent entry in the configuration transition table indicates an error. * Unfortunately, that means that we need to always build the full table. This method allows a * transition to indicate that a non-existent entry indicates a self transition, i.e., that the * resulting configuration is the same as the current configuration. This can simplify the code * needed to set up the transition table. */ boolean defaultsToSelf(); } /** * A configuration split transition; this should be used to transition to multiple configurations * simultaneously. Note that the corresponding rule implementations must have special support to * handle this. * * <p>{@code T} must always be {@code BuildOptions}, but it can't be defined that way because * the symbol isn't available here. */ // TODO(bazel-team): Serializability constraints? @ThreadSafety.Immutable public interface SplitTransition<T> extends Transition { /** * Return the list of {@code BuildOptions} after splitting; empty if not applicable. */ List<T> split(T buildOptions); } /** * Declaration how the configuration should change when following a label or label list attribute. */ public enum ConfigurationTransition implements Transition { /** No transition, i.e., the same configuration as the current. */ NONE, /** Transition to the host configuration. */ HOST, /** Transition to a null configuration (applies to, e.g., input files). */ NULL, /** Transition from the target configuration to the data configuration. */ // TODO(bazel-team): Move this elsewhere. DATA, /** * Transition to one or more configurations. To obtain the actual child configurations, * invoke {@link Attribute#getSplitTransition(Rule)}. See {@link SplitTransition}. **/ SPLIT(true); private boolean defaultsToSelf; ConfigurationTransition() { this(false); } ConfigurationTransition(boolean defaultsToSelf) { this.defaultsToSelf = defaultsToSelf; } @Override public boolean defaultsToSelf() { return defaultsToSelf; } } 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; } }; /** * Using this callback function, rules can change the configuration of their dependencies during * the analysis phase. * * <p>If dynamic configurations are enabled, the returned transition must be a * {@link com.google.devtools.build.lib.analysis.config.PatchTransition}. * * @deprecated this is only needed for statically configured builds. Dynamically configured builds * should just use {@link Attribute.Builder#cfg(Transition)}} with a directly provided * {@link com.google.devtools.build.lib.analysis.config.PatchTransition}. */ @Deprecated public interface Configurator<TBuildOptions> { Transition apply(TBuildOptions fromOptions); } /** * 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 originating rule. */ SplitTransition<?> apply(Rule fromRule); } /** * 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(Rule fromRule) { return splitTransition; } } /** * A predicate class to check if the value of the attribute comes from a predefined set. */ public static class AllowedValueSet implements PredicateWithMessage<Object> { private final Set<Object> allowedValues; public <T> AllowedValueSet(T... values) { this(Arrays.asList(values)); } public AllowedValueSet(Iterable<?> values) { Preconditions.checkNotNull(values); Preconditions.checkArgument(!Iterables.isEmpty(values)); // Do not remove <Object>: workaround for Java 7 type inference. allowedValues = ImmutableSet.<Object>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<Object> getAllowedValues() { return allowedValues; } } public ImmutableMap<String, ImmutableSet<String>> getRequiredAspectParameters() { ImmutableMap.Builder<String, ImmutableSet<String>> 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 <TYPE> attribute type class */ public static <TYPE> Attribute.Builder<TYPE> attr(String name, Type<TYPE> type) { return new Builder<>(name, type); } /** * A fluent builder for the {@code Attribute} instances. * * <p>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 <TYPE> { private final String name; private final Type<TYPE> type; private Transition configTransition = ConfigurationTransition.NONE; private Predicate<RuleClass> allowedRuleClassesForLabels = Predicates.alwaysTrue(); private Predicate<RuleClass> allowedRuleClassesForLabelsWarning = Predicates.alwaysFalse(); private Configurator<?> configurator; private SplitTransitionProvider splitTransitionProvider; private FileTypeSet allowedFileTypesForLabels; private ValidityPredicate validityPredicate = ANY_EDGE; private Object value; private AttributeValueSource valueSource = AttributeValueSource.DIRECT; private boolean valueSet; private Predicate<AttributeMap> condition; private Set<PropertyFlag> propertyFlags = EnumSet.noneOf(PropertyFlag.class); private PredicateWithMessage<Object> allowedValues = null; private ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> mandatoryProvidersList = ImmutableList.<ImmutableSet<SkylarkProviderIdentifier>>of(); private ImmutableList<ImmutableList<Class<? extends TransitiveInfoProvider>>> mandatoryNativeProvidersList = ImmutableList.of(); private HashMap<String, RuleAspect<?>> 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> type) { this.name = Preconditions.checkNotNull(name); this.type = Preconditions.checkNotNull(type); if (isImplicit(name) || isLateBound(name)) { setPropertyFlag(PropertyFlag.UNDOCUMENTED, "undocumented"); } } private Builder<TYPE> 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<TYPE> 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<TYPE> 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<TYPE> 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<TYPE> 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<TYPE> 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<TYPE> 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<TYPE> 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<TYPE> 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<TYPE> cfg(SplitTransitionProvider splitTransitionProvider) { Preconditions.checkState(this.configTransition == ConfigurationTransition.NONE, "the configuration transition is already set"); this.splitTransitionProvider = Preconditions.checkNotNull(splitTransitionProvider); this.configTransition = ConfigurationTransition.SPLIT; return this; } /** * Defines the configuration transition for this attribute. Defaults to * {@code NONE}. */ public Builder<TYPE> cfg(SplitTransition<?> configTransition) { return cfg(new BasicSplitTransitionProvider(Preconditions.checkNotNull(configTransition))); } /** * Defines the configuration transition for this attribute. Defaults to * {@code NONE}. */ public Builder<TYPE> cfg(Transition configTransition) { Preconditions.checkState(this.configTransition == ConfigurationTransition.NONE, "the configuration transition is already set"); Preconditions.checkArgument(configTransition != ConfigurationTransition.SPLIT, "split transitions must be defined using the SplitTransition object"); if (configTransition instanceof SplitTransition) { return cfg((SplitTransition<?>) configTransition); } else { this.configTransition = configTransition; return this; } } /** * @deprecated Use {@link #cfg(Transition)}} with a * {@link com.google.devtools.build.lib.analysis.config.PatchTransition} instead. This method * only provides legacy support for statically configured builds. */ @Deprecated public Builder<TYPE> cfg(Configurator<?> configurator) { this.configurator = configurator; return this; } /** * Requires the attribute target to be executable; only for label or label * list attributes. Defaults to {@code false}. */ public Builder<TYPE> 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<TYPE> 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<TYPE> 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. * * <p>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<TYPE> 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. * * <p>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. */ public Builder<TYPE> defaultValue(Object defaultValue, Object context) throws ConversionException { Preconditions.checkState(!valueSet, "the default value is already set"); value = type.convert(defaultValue, "attribute " + name, context); valueSet = true; return this; } /** See value(TYPE) above. This method is only meant for Skylark usage. */ public Builder<TYPE> defaultValue(Object defaultValue) throws ConversionException { return defaultValue(defaultValue, 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. * * <p>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<TYPE> 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. * * <p>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. * * <p>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<TYPE> 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. */ public Builder<TYPE> value(LateBoundDefault<?> defaultValue) { Preconditions.checkState(!valueSet, "the default value is already set"); Preconditions.checkState(name.isEmpty() || isLateBound(name)); 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. * * <p>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<TYPE> condition(Predicate<AttributeMap> 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<TYPE> taggable() { return setPropertyFlag(PropertyFlag.TAGGABLE, "taggable"); } /** * Disables dependency checks done by * {@link com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.PrerequisiteValidator}. */ public Builder<TYPE> 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. * * <p>Most attributes are enforced by default, so in the common case this call is unnecessary. * * <p>See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics#getConstraintCheckedDependencies} * for enforcement policy details. */ public Builder<TYPE> 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. * * <p>See {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics#getConstraintCheckedDependencies} * for enforcement policy details. */ public Builder<TYPE> 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 an error is produced during * the analysis phase. Defaults to allow any types. * * <p>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<TYPE> allowedRuleClasses(Iterable<String> allowedRuleClasses) { return allowedRuleClasses( new RuleClass.Builder.RuleClassNamePredicate(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 an error is produced during * the analysis phase. Defaults to allow any types. * * <p>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<TYPE> allowedRuleClasses(Predicate<RuleClass> 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 an error is produced during * the analysis phase. Defaults to allow any types. * * <p>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<TYPE> 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. * * <p>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<TYPE> 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<TYPE> 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. * * <p>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<TYPE> 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. If the attribute * contains Labels of any other rule type (other than this or those set in * allowedRuleClasses()), then a warning is produced during * the analysis phase. Defaults to deny any types. * * <p>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<TYPE> allowedRuleClassesWithWarning(Collection<String> allowedRuleClasses) { return allowedRuleClassesWithWarning( new RuleClass.Builder.RuleClassNamePredicate(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 (other than this or those set in * allowedRuleClasses()), then a warning is produced during * the analysis phase. Defaults to deny any types. * * <p>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<TYPE> allowedRuleClassesWithWarning(Predicate<RuleClass> 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 for the labels occurring in the attribute. If the attribute * contains Labels of any other rule type (other than this or those set in * allowedRuleClasses()), then a warning is produced during * the analysis phase. Defaults to deny any types. * * <p>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<TYPE> 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<TYPE> mandatoryNativeProvidersList( Iterable<? extends Iterable<Class<? extends TransitiveInfoProvider>>> providersList) { Preconditions.checkState(type.getLabelClass() == LabelClass.DEPENDENCY, "must be a label-valued type"); ImmutableList.Builder<ImmutableList<Class<? extends TransitiveInfoProvider>>> listBuilder = ImmutableList.builder(); for (Iterable<Class<? extends TransitiveInfoProvider>> providers : providersList) { listBuilder.add(ImmutableList.<Class<? extends TransitiveInfoProvider>>copyOf(providers)); } this.mandatoryNativeProvidersList = listBuilder.build(); return this; } public Builder<TYPE> mandatoryNativeProviders( Iterable<Class<? extends TransitiveInfoProvider>> 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<TYPE> mandatoryProvidersList( Iterable<? extends Iterable<SkylarkProviderIdentifier>> providersList){ Preconditions.checkState(type.getLabelClass() == LabelClass.DEPENDENCY, "must be a label-valued type"); ImmutableList.Builder<ImmutableSet<SkylarkProviderIdentifier>> listBuilder = ImmutableList.builder(); for (Iterable<SkylarkProviderIdentifier> providers : providersList) { listBuilder.add(ImmutableSet.copyOf(providers)); } this.mandatoryProvidersList = listBuilder.build(); return this; } public Builder<TYPE> legacyMandatoryProviders(String... ids) { return mandatoryProviders( Iterables.transform(Arrays.asList(ids), new Function<String, SkylarkProviderIdentifier>() { @Override public SkylarkProviderIdentifier apply(String s) { Preconditions.checkNotNull(s); return SkylarkProviderIdentifier.forLegacy(s); } })); } public Builder<TYPE> mandatoryProviders(Iterable<SkylarkProviderIdentifier> providers) { if (providers.iterator().hasNext()) { mandatoryProvidersList(ImmutableList.of(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<TYPE> aspect( NativeAspectClass aspect, Function<Rule, AspectParameters> 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<TYPE> aspect(NativeAspectClass aspect) { Function<Rule, AspectParameters> noParameters = new Function<Rule, AspectParameters>() { @Override public AspectParameters apply(Rule input) { return AspectParameters.EMPTY; } }; return this.aspect(aspect, noParameters); } public Builder<TYPE> aspect( SkylarkAspect 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<TYPE> 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<TYPE> 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<TYPE> allowedValues(PredicateWithMessage<Object> 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. * * <p>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<TYPE> 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"); // 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; } } return new Attribute( name, type, Sets.immutableEnumSet(propertyFlags), valueSet ? value : type.getDefaultValue(), configTransition, configurator, splitTransitionProvider, allowedRuleClassesForLabels, allowedRuleClassesForLabelsWarning, allowedFileTypesForLabels, validityPredicate, condition, allowedValues, mandatoryProvidersList, mandatoryNativeProvidersList, ImmutableList.copyOf(aspects.values())); } } /** * A strategy for dealing with too many computations, used when creating lookup tables for {@link * ComputedDefault}s. * * @param <TException> The type of exception this strategy throws if too many computations are * attempted. */ interface ComputationLimiter<TException extends Exception> { 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<RuntimeException> NULL_COMPUTATION_LIMITER = new ComputationLimiter<RuntimeException>() { @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<TooManyConfigurableAttributesException> { /** 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. * * <p>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<TComputeException extends Exception> { abstract Object compute(AttributeMap map) throws TComputeException; /** * Returns a lookup table mapping from: * * <ul> * <li>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) * </ul> * * <p>to: * * <ul> * <li>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. * </ul> * * <p>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. * * <p>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. */ <T, TLimitException extends Exception> Map<List<Object>, T> computeValuesForAllCombinations( List<String> dependencies, Type<T> type, Rule rule, ComputationLimiter<TLimitException> limiter) throws TComputeException, TLimitException { // This will hold every (value1, value2, ..) combination of the declared dependencies. // Collect those combinations. AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule); List<Map<String, Object>> depMaps = mapper.visitAttributes(dependencies, limiter); // For each combination, call compute() on a specialized AttributeMap providing those // values. Map<List<Object>, T> valueMap = new HashMap<>(depMaps.size()); for (Map<String, Object> depMap : depMaps) { AttributeMap attrMap = mapper.createMapBackedAttributeMap(depMap); Object value = compute(attrMap); List<Object> 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<Object> createDependencyAssignmentTuple( List<String> dependencies, AttributeMap attrMap) { ArrayList<Object> 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. * * <p>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. * * <p>If a computed default reads the value of another attribute, at least one of the following * must be true: * * <ol> * <li>The other attribute must be declared in the computed default's constructor * <li>The other attribute must be non-configurable ({@link Builder#nonconfigurable} * </ol> * * <p>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. * * <p>Implementations of this interface must be immutable. */ public abstract static class ComputedDefault { private final ImmutableList<String> dependencies; /** * Create a computed default that can read all non-configurable attribute values and no * configurable attribute values. */ public ComputedDefault() { this(ImmutableList.<String>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. * * <p>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<String> dependencies) { // Order is important for #createDependencyAssignmentTuple. this.dependencies = Ordering.natural().immutableSortedCopy(dependencies); } <T> Iterable<T> getPossibleValues(Type<T> type, Rule rule) { final ComputedDefault owner = ComputedDefault.this; ComputationStrategy<RuntimeException> strategy = new ComputationStrategy<RuntimeException>() { @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<String> 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<String> 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<String> 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}. * * <p>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}. * * <p>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<EvalException> caughtEvalExceptionIfAny = new AtomicReference<>(); ComputationStrategy<InterruptedException> strategy = new ComputationStrategy<InterruptedException>() { @Override public Object compute(AttributeMap map) throws InterruptedException { try { return owner.computeValue(map); } catch (EvalException ex) { caughtEvalExceptionIfAny.compareAndSet(null, ex); return null; } } }; ImmutableList.Builder<Type<?>> dependencyTypesBuilder = ImmutableList.builder(); Map<List<Object>, 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(AttributeMap rule) throws EvalException, InterruptedException { Map<String, Object> 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(attrValues); } private Object invokeCallback(Map<String, Object> attrValues) throws EvalException, InterruptedException { ClassObject attrs = NativeClassObjectConstructor.STRUCT.create( attrValues, "No such regular (non computed) attribute '%s'."); Object result = callback.call(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. * * <p>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<Type<?>> dependencyTypes; private final Map<List<Object>, 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<String> requiredAttributes, ImmutableList<Type<?>> dependencyTypes, Map<List<Object>, Object> lookupTable) { super(Preconditions.checkNotNull(requiredAttributes)); this.dependencyTypes = Preconditions.checkNotNull(dependencyTypes); this.lookupTable = Preconditions.checkNotNull(lookupTable); } List<Type<?>> getDependencyTypes() { return dependencyTypes; } Map<List<Object>, Object> getLookupTable() { return lookupTable; } @Override public Object getDefault(AttributeMap rule) { List<Object> 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 <T> Iterable<T> getPossibleValues(Type<T> type, Rule rule) { List<T> result = new ArrayList<>(lookupTable.size()); for (Object obj : lookupTable.values()) { result.add(type.cast(obj)); } return result; } } /** * Marker interface for late-bound values. Unfortunately, we can't refer to BuildConfiguration * right now, since that is in a separate compilation unit. * * <p>Implementations of this interface must be immutable. * * <p>Use sparingly - having different values for attributes during loading and analysis can * confuse users. */ public interface LateBoundDefault<T> { /** * 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. * * <p>This method only sets the configuration passed to {@link #resolve}. If you want the * dependency to also be analyzed in the host configuration, use * {@link ConfigurationTransition#HOST}. */ boolean useHostConfiguration(); /** * Returns the set of required configuration fragments, i.e., fragments that will be accessed by * the code. */ Set<Class<?>> getRequiredConfigurationFragments(); /** * The default value for the attribute that is set during the loading phase. */ Object getDefault(); /** * 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 o the configuration to evaluate with */ Object resolve(Rule rule, AttributeMap attributes, T o) throws EvalException, InterruptedException; } /** * Abstract super class for label-typed {@link LateBoundDefault} implementations that simplifies * the client code a little and makes it a bit more type-safe. */ public abstract static class LateBoundLabel<T> implements LateBoundDefault<T> { private final Label label; private final ImmutableSet<Class<?>> requiredConfigurationFragments; public LateBoundLabel() { this((Label) null); } public LateBoundLabel(Class<?>... requiredConfigurationFragments) { this((Label) null, requiredConfigurationFragments); } public LateBoundLabel(Label label) { this.label = label; this.requiredConfigurationFragments = ImmutableSet.of(); } public LateBoundLabel(Label label, Class<?>... requiredConfigurationFragments) { this.label = label; this.requiredConfigurationFragments = ImmutableSet.copyOf(requiredConfigurationFragments); } public LateBoundLabel(String label) { this(Label.parseAbsoluteUnchecked(label)); } public LateBoundLabel(String label, Class<?>... requiredConfigurationFragments) { this(Label.parseAbsoluteUnchecked(label), requiredConfigurationFragments); } @Override public boolean useHostConfiguration() { return false; } @Override public ImmutableSet<Class<?>> getRequiredConfigurationFragments() { return requiredConfigurationFragments; } @Override public final Label getDefault() { return label; } @Override public abstract Label resolve(Rule rule, AttributeMap attributes, T configuration); } /** * Abstract super class for label-list-typed {@link LateBoundDefault} implementations that * simplifies the client code a little and makes it a bit more type-safe. */ public abstract static class LateBoundLabelList<T> implements LateBoundDefault<T> { private final ImmutableList<Label> labels; private final ImmutableSet<Class<?>> requiredConfigurationFragments; public LateBoundLabelList(Class<?>... requiredConfigurationFragments) { this(ImmutableList.<Label>of(), requiredConfigurationFragments); } public LateBoundLabelList(List<Label> labels, Class<?>... requiredConfigurationFragments) { this.labels = ImmutableList.copyOf(labels); this.requiredConfigurationFragments = ImmutableSet.copyOf(requiredConfigurationFragments); } @Override public boolean useHostConfiguration() { return false; } @Override public ImmutableSet<Class<?>> getRequiredConfigurationFragments() { return requiredConfigurationFragments; } @Override public final List<Label> getDefault() { return labels; } @Override public abstract List<Label> resolve(Rule rule, AttributeMap attributes, T configuration); } private final String name; private final Type<?> type; private final Set<PropertyFlag> 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 Transition configTransition; private final Configurator<?> configurator; 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 Predicate<RuleClass> 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 Predicate<RuleClass> 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<AttributeMap> condition; private final PredicateWithMessage<Object> allowedValues; private final ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> mandatoryProvidersList; private final ImmutableList<ImmutableList<Class<? extends TransitiveInfoProvider>>> mandatoryNativeProvidersList; private final ImmutableList<RuleAspect<?>> 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<PropertyFlag> propertyFlags, Object defaultValue, Transition configTransition, Configurator<?> configurator, SplitTransitionProvider splitTransitionProvider, Predicate<RuleClass> allowedRuleClassesForLabels, Predicate<RuleClass> allowedRuleClassesForLabelsWarning, FileTypeSet allowedFileTypesForLabels, ValidityPredicate validityPredicate, Predicate<AttributeMap> condition, PredicateWithMessage<Object> allowedValues, ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> mandatoryProvidersList, ImmutableList<ImmutableList<Class<? extends TransitiveInfoProvider>>> mandatoryNativeProvidersList, ImmutableList<RuleAspect<?>> aspects) { Preconditions.checkNotNull(configTransition); Preconditions.checkArgument( (configTransition == ConfigurationTransition.NONE && configurator == null) || 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((configurator == null), "a late bound attribute cannot specify a configurator"); Preconditions.checkArgument(!lateBoundDefault.useHostConfiguration() || (configTransition == ConfigurationTransition.HOST), "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.configurator = configurator; this.splitTransitionProvider = splitTransitionProvider; this.allowedRuleClassesForLabels = allowedRuleClassesForLabels; this.allowedRuleClassesForLabelsWarning = allowedRuleClassesForLabelsWarning; this.allowedFileTypesForLabels = allowedFileTypesForLabels; this.validityPredicate = validityPredicate; this.condition = condition; this.allowedValues = allowedValues; this.mandatoryProvidersList = mandatoryProvidersList; this.mandatoryNativeProvidersList = mandatoryNativeProvidersList; 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 Transition getConfigurationTransition() { return configTransition; } /** * Returns the configurator instance for this attribute for label or label list attributes. * For other attributes it will always return {@code null}. */ public Configurator<?> getConfigurator() { return configurator; } /** * Returns the split configuration transition for this attribute. * * @param rule the originating {@link Rule} which owns this attribute * @return a SplitTransition<BuildOptions> object * @throws IllegalStateException if {@link #hasSplitConfigurationTransition} is not true */ public SplitTransition<?> getSplitTransition(Rule rule) { Preconditions.checkState(hasSplitConfigurationTransition()); return splitTransitionProvider.apply(rule); } /** * 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. */ public Predicate<RuleClass> getAllowedRuleClassesPredicate() { return allowedRuleClassesForLabels; } /** * 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<RuleClass> getAllowedRuleClassesWarningPredicate() { return allowedRuleClassesForLabelsWarning; } /** * Returns the list of sets of mandatory Skylark providers. */ public ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> getMandatoryProvidersList() { return mandatoryProvidersList; } /** Returns the list of lists of mandatory native providers. */ public ImmutableList<ImmutableList<Class<? extends TransitiveInfoProvider>>> getMandatoryNativeProvidersList() { return mandatoryNativeProvidersList; } public FileTypeSet getAllowedFileTypesPredicate() { return allowedFileTypesForLabels; } public ValidityPredicate getValidityPredicate() { return validityPredicate; } public Predicate<AttributeMap> getCondition() { return condition == null ? Predicates.<AttributeMap>alwaysTrue() : condition; } public PredicateWithMessage<Object> getAllowedValues() { return allowedValues; } /** * Returns the list of aspects required for dependencies through this attribute. */ public ImmutableList<Aspect> getAspects(Rule rule) { ImmutableList.Builder<Aspect> builder = ImmutableList.builder(); for (RuleAspect aspect : aspects) { Aspect a = aspect.getAspect(rule); if (a != null) { builder.add(a); } } return builder.build(); } public ImmutableList<AspectClass> getAspectClasses() { ImmutableList.Builder<AspectClass> 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. * * <p>The result may be null (although this is not a value in the build * language). * * <p>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 <TYPE> Attribute.Builder<TYPE> cloneBuilder(Type<TYPE> tp) { Preconditions.checkArgument(tp == this.type); Builder<TYPE> builder = new Builder<>(name, tp); builder.allowedFileTypesForLabels = allowedFileTypesForLabels; builder.allowedRuleClassesForLabels = allowedRuleClassesForLabels; builder.allowedRuleClassesForLabelsWarning = allowedRuleClassesForLabelsWarning; builder.mandatoryNativeProvidersList = mandatoryNativeProvidersList; builder.mandatoryProvidersList = mandatoryProvidersList; 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); } }