// 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.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.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.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 Predicate ANY_RULE = Predicates.alwaysTrue(); public static final Predicate NO_RULE = Predicates.alwaysFalse(); /** * 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 SkylarkAspect aspect; public SkylarkRuleAspect(SkylarkAspect 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; } } /** * 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. * *

{@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 extends Transition { /** * Return the list of {@code BuildOptions} after splitting; empty if not applicable. */ List 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 final 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. * *

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 { 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 { 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 Transition configTransition = ConfigurationTransition.NONE; private Predicate allowedRuleClassesForLabels = Predicates.alwaysTrue(); private Predicate 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 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 == 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 cfg(SplitTransition configTransition) { return cfg(new BasicSplitTransitionProvider(Preconditions.checkNotNull(configTransition))); } /** * Defines the configuration transition for this attribute. Defaults to * {@code NONE}. */ public Builder 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 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 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. */ public Builder 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. * *

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( new 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 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(Predicate 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( new RuleClassNamePredicate(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(Predicate 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( 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 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"); // 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; } } if (allowedRuleClassesForLabels instanceof RuleClassNamePredicate && allowedRuleClassesForLabelsWarning instanceof RuleClassNamePredicate) { Preconditions.checkState( !((RuleClassNamePredicate) allowedRuleClassesForLabels) .intersects((RuleClassNamePredicate) allowedRuleClassesForLabelsWarning), "allowedRuleClasses and allowedRuleClassesWithWarning may not contain " + "the same rule classes"); } return new Attribute( name, type, Sets.immutableEnumSet(propertyFlags), valueSet ? value : type.getDefaultValue(), configTransition, configurator, 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(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(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(attrValues); } private Object invokeCallback(Map attrValues) throws EvalException, InterruptedException { ClassObject attrs = NativeProvider.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. * *

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; } } /** * Marker interface for late-bound values. Unfortunately, we can't refer to BuildConfiguration * right now, since that is in a separate compilation unit. * *

Implementations of this interface must be immutable. * *

Use sparingly - having different values for attributes during loading and analysis can * confuse users. */ public interface LateBoundDefault { /** * 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. * *

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> 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 implements LateBoundDefault { private final Label label; private final ImmutableSet> 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> 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 implements LateBoundDefault { private final ImmutableList