// 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.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST; import static com.google.devtools.build.lib.packages.Attribute.attr; import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; import static com.google.devtools.build.lib.syntax.Type.BOOLEAN; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Ordering; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.events.NullEventHandler; import com.google.devtools.build.lib.packages.Attribute.SkylarkComputedDefaultTemplate; import com.google.devtools.build.lib.packages.Attribute.SkylarkComputedDefaultTemplate.CannotPrecomputeDefaultsException; import com.google.devtools.build.lib.packages.Attribute.Transition; import com.google.devtools.build.lib.packages.BuildType.SelectorList; import com.google.devtools.build.lib.packages.ConfigurationFragmentPolicy.MissingFragmentPolicy; import com.google.devtools.build.lib.packages.RuleFactory.AttributeValuesMap; import com.google.devtools.build.lib.syntax.Argument; import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.GlobList; import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.syntax.Type.ConversionException; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.util.StringUtil; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** * Instances of RuleClass encapsulate the set of attributes of a given "class" of rule, such as * cc_binary. * *

This is an instance of the "meta-class" pattern for Rules: we achieve using values * what subclasses achieve using types. (The "Design Patterns" book doesn't include this * pattern, so think of it as something like a cross between a Flyweight and a State pattern. Like * Flyweight, we avoid repeatedly storing data that belongs to many instances. Like State, we * delegate from Rule to RuleClass for the specific behavior of that rule (though unlike state, a * Rule object never changes its RuleClass). This avoids the need to declare one Java class per * class of Rule, yet achieves the same behavior.) * *

The use of a metaclass also allows us to compute a mapping from Attributes to small integers * and share this between all rules of the same metaclass. This means we can save the attribute * dictionary for each rule instance using an array, which is much more compact than a hashtable. * *

Rule classes whose names start with "$" are considered "abstract"; since they are not valid * identifiers, they cannot be named in the build language. However, they are useful for grouping * related attributes which are inherited. * *

The exact values in this class are important. In particular: *

*/ // Non-final only for mocking in tests. Do not subclass! @Immutable public class RuleClass { static final Function> NO_EXTERNAL_BINDINGS = Functions.>constant(ImmutableMap.of()); static final Function> NO_OPTION_REFERENCE = Functions.>constant(ImmutableSet.of()); public static final PathFragment THIRD_PARTY_PREFIX = PathFragment.create("third_party"); /** * A constraint for the package name of the Rule instances. */ public static class PackageNameConstraint implements PredicateWithMessage { public static final int ANY_SEGMENT = 0; private final int pathSegment; private final Set values; /** * The pathSegment-th segment of the package must be one of the specified values. * The path segment indexing starts from 1. */ public PackageNameConstraint(int pathSegment, String... values) { this.values = ImmutableSet.copyOf(values); this.pathSegment = pathSegment; } @Override public boolean apply(Rule input) { PathFragment path = input.getLabel().getPackageFragment(); if (pathSegment == ANY_SEGMENT) { return path.getFirstSegment(values) != PathFragment.INVALID_SEGMENT; } else { return path.segmentCount() >= pathSegment && values.contains(path.getSegment(pathSegment - 1)); } } @Override public String getErrorReason(Rule param) { if (pathSegment == ANY_SEGMENT) { return param.getRuleClass() + " rules have to be under a " + StringUtil.joinEnglishList(values, "or", "'") + " directory"; } else if (pathSegment == 1) { return param.getRuleClass() + " rules are only allowed in " + StringUtil.joinEnglishList(StringUtil.append(values, "//", ""), "or"); } else { return param.getRuleClass() + " rules are only allowed in packages which " + StringUtil.ordinal(pathSegment) + " is " + StringUtil.joinEnglishList(values, "or"); } } @VisibleForTesting public int getPathSegment() { return pathSegment; } @VisibleForTesting public Collection getValues() { return values; } } /** * Using this callback function, rules can override their own configuration during the * analysis phase. */ public interface Configurator { TConfig apply(TRule rule, TConfig configuration); /** * Describes the Bazel feature this configurator is used for. Used for checking that dynamic * configuration transitions are only applied to expected configurator types. */ String getCategory(); } /** * A factory or builder class for rule implementations. */ public interface ConfiguredTargetFactory { /** * Returns a fully initialized configured target instance using the given context. * * @throws RuleErrorException if configured target creation could not be completed due to rule * errors */ TConfiguredTarget create(TContext ruleContext) throws InterruptedException, RuleErrorException; /** * Exception indicating that configured target creation could not be completed. Error messaging * should be done via {@link RuleErrorConsumer}; this exception only interrupts configured * target creation in cases where it can no longer continue. */ public static final class RuleErrorException extends Exception {} } /** * Default rule configurator, it doesn't change the assigned configuration. */ public static final RuleClass.Configurator NO_CHANGE = new RuleClass.Configurator() { @Override public Object apply(Object rule, Object configuration) { return configuration; } @Override public String getCategory() { return "core"; } }; /** * For Bazel's constraint system: the attribute that declares the set of environments a rule * supports, overriding the defaults for their respective groups. */ public static final String RESTRICTED_ENVIRONMENT_ATTR = "restricted_to"; /** * For Bazel's constraint system: the attribute that declares the set of environments a rule * supports, appending them to the defaults for their respective groups. */ public static final String COMPATIBLE_ENVIRONMENT_ATTR = "compatible_with"; /** * For Bazel's constraint system: the implicit attribute used to store rule class restriction * defaults as specified by {@link Builder#restrictedTo}. */ public static final String DEFAULT_RESTRICTED_ENVIRONMENT_ATTR = "$" + RESTRICTED_ENVIRONMENT_ATTR; /** * For Bazel's constraint system: the implicit attribute used to store rule class compatibility * defaults as specified by {@link Builder#compatibleWith}. */ public static final String DEFAULT_COMPATIBLE_ENVIRONMENT_ATTR = "$" + COMPATIBLE_ENVIRONMENT_ATTR; /** * A support class to make it easier to create {@code RuleClass} instances. * This class follows the 'fluent builder' pattern. * *

The {@link #addAttribute} method will throw an exception if an attribute * of that name already exists. Use {@link #overrideAttribute} in that case. */ public static final class Builder { private static final Pattern RULE_NAME_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*"); /** * The type of the rule class, which determines valid names and required * attributes. */ public enum RuleClassType { /** * Abstract rules are intended for rule classes that are just used to * factor out common attributes, and for rule classes that are used only * internally. These rules cannot be instantiated by a BUILD file. * *

The rule name must contain a '$' and {@link * TargetUtils#isTestRuleName} must return false for the name. */ ABSTRACT { @Override public void checkName(String name) { Preconditions.checkArgument( (name.contains("$") && !TargetUtils.isTestRuleName(name)) || name.isEmpty()); } @Override public void checkAttributes(Map attributes) { // No required attributes. } }, /** * Invisible rule classes should contain a dollar sign so that they cannot be instantiated * by the user. They are different from abstract rules in that they can be instantiated * at will. */ INVISIBLE { @Override public void checkName(String name) { Preconditions.checkArgument(name.contains("$")); } @Override public void checkAttributes(Map attributes) { // No required attributes. } }, /** * Normal rules are instantiable by BUILD files. Their names must therefore * obey the rules for identifiers in the BUILD language. In addition, * {@link TargetUtils#isTestRuleName} must return false for the name. */ NORMAL { @Override public void checkName(String name) { Preconditions.checkArgument( !TargetUtils.isTestRuleName(name) && RULE_NAME_PATTERN.matcher(name).matches(), "Invalid rule name: %s", name); } @Override public void checkAttributes(Map attributes) { for (Attribute attribute : REQUIRED_ATTRIBUTES_FOR_NORMAL_RULES) { Attribute presentAttribute = attributes.get(attribute.getName()); Preconditions.checkState(presentAttribute != null, "Missing mandatory '%s' attribute in normal rule class.", attribute.getName()); Preconditions.checkState(presentAttribute.getType().equals(attribute.getType()), "Mandatory attribute '%s' in normal rule class has incorrect type (expected" + " %s).", attribute.getName(), attribute.getType()); } } }, /** * Workspace rules can only be instantiated from a WORKSPACE file. Their names obey the * rule for identifiers. */ WORKSPACE { @Override public void checkName(String name) { Preconditions.checkArgument(RULE_NAME_PATTERN.matcher(name).matches()); } @Override public void checkAttributes(Map attributes) { // No required attributes. } }, /** * Test rules are instantiable by BUILD files and are handled specially * when run with the 'test' command. Their names must obey the rules * for identifiers in the BUILD language and {@link * TargetUtils#isTestRuleName} must return true for the name. * *

In addition, test rules must contain certain attributes. See {@link * Builder#REQUIRED_ATTRIBUTES_FOR_TESTS}. */ TEST { @Override public void checkName(String name) { Preconditions.checkArgument(TargetUtils.isTestRuleName(name) && RULE_NAME_PATTERN.matcher(name).matches()); } @Override public void checkAttributes(Map attributes) { for (Attribute attribute : REQUIRED_ATTRIBUTES_FOR_TESTS) { Attribute presentAttribute = attributes.get(attribute.getName()); Preconditions.checkState(presentAttribute != null, "Missing mandatory '%s' attribute in test rule class.", attribute.getName()); Preconditions.checkState(presentAttribute.getType().equals(attribute.getType()), "Mandatory attribute '%s' in test rule class has incorrect type (expcected %s).", attribute.getName(), attribute.getType()); } } }, /** * Placeholder rules are only instantiated when packages which refer to non-native rule * classes are deserialized. At this time, non-native rule classes can't be serialized. To * prevent crashes on deserialization, when a package containing a rule with a non-native rule * class is deserialized, the rule is assigned a placeholder rule class. This is compatible * with our limited set of package serialization use cases. * * Placeholder rule class names obey the rule for identifiers. */ PLACEHOLDER { @Override public void checkName(String name) { Preconditions.checkArgument(RULE_NAME_PATTERN.matcher(name).matches(), name); } @Override public void checkAttributes(Map attributes) { // No required attributes; this rule class cannot have the wrong set of attributes now // because, if it did, the rule class would have failed to build before the package // referring to it was serialized. } }; /** * Checks whether the given name is valid for the current rule class type. * * @throws IllegalArgumentException if the name is not valid */ public abstract void checkName(String name); /** * Checks whether the given set of attributes contains all the required * attributes for the current rule class type. * * @throws IllegalArgumentException if a required attribute is missing */ public abstract void checkAttributes(Map attributes); } /** * A predicate that filters rule classes based on their names. */ public static class RuleClassNamePredicate implements Predicate { private final Set ruleClasses; public RuleClassNamePredicate(Iterable ruleClasses) { this.ruleClasses = ImmutableSet.copyOf(ruleClasses); } public RuleClassNamePredicate(String... ruleClasses) { this.ruleClasses = ImmutableSet.copyOf(ruleClasses); } public RuleClassNamePredicate() { this(ImmutableSet.of()); } @Override public boolean apply(RuleClass ruleClass) { return ruleClasses.contains(ruleClass.getName()); } @Override public int hashCode() { return ruleClasses.hashCode(); } @Override public boolean equals(Object o) { return (o instanceof RuleClassNamePredicate) && ruleClasses.equals(((RuleClassNamePredicate) o).ruleClasses); } @Override public String toString() { return ruleClasses.isEmpty() ? "nothing" : StringUtil.joinEnglishList(ruleClasses); } } /** * A RuleTransitionFactory which always returns the same transition. */ private static final class FixedTransitionFactory implements RuleTransitionFactory { private final Transition transition; private FixedTransitionFactory(Transition transition) { this.transition = transition; } @Override public Transition buildTransitionFor(Rule rule) { return transition; } } /** List of required attributes for normal rules, name and type. */ public static final ImmutableList REQUIRED_ATTRIBUTES_FOR_NORMAL_RULES = ImmutableList.of(attr("tags", Type.STRING_LIST).build()); /** List of required attributes for test rules, name and type. */ public static final ImmutableList REQUIRED_ATTRIBUTES_FOR_TESTS = ImmutableList.of( attr("tags", Type.STRING_LIST).build(), attr("size", Type.STRING).build(), attr("timeout", Type.STRING).build(), attr("flaky", Type.BOOLEAN).build(), attr("shard_count", Type.INTEGER).build(), attr("local", Type.BOOLEAN).build()); private String name; private final RuleClassType type; private final boolean skylark; private boolean skylarkTestable = false; private boolean documented; private boolean publicByDefault = false; private boolean binaryOutput = true; private boolean workspaceOnly = false; private boolean outputsDefaultExecutable = false; private boolean isConfigMatcher = false; private ImplicitOutputsFunction implicitOutputsFunction = ImplicitOutputsFunction.NONE; private Configurator configurator = NO_CHANGE; private RuleTransitionFactory transitionFactory; private ConfiguredTargetFactory configuredTargetFactory = null; private PredicateWithMessage validityPredicate = PredicatesWithMessage.alwaysTrue(); private Predicate preferredDependencyPredicate = Predicates.alwaysFalse(); private AdvertisedProviderSet.Builder advertisedProviders = AdvertisedProviderSet.builder(); private BaseFunction configuredTargetFunction = null; private Function> externalBindingsFunction = NO_EXTERNAL_BINDINGS; private Function> optionReferenceFunction = NO_OPTION_REFERENCE; @Nullable private Environment ruleDefinitionEnvironment = null; @Nullable private String ruleDefinitionEnvironmentHashCode = null; private ConfigurationFragmentPolicy.Builder configurationFragmentPolicy = new ConfigurationFragmentPolicy.Builder(); private boolean supportsConstraintChecking = true; private final Map attributes = new LinkedHashMap<>(); private final List requiredToolchains = new ArrayList<>(); /** * Constructs a new {@code RuleClassBuilder} using all attributes from all * parent rule classes. An attribute cannot exist in more than one parent. * *

The rule type affects the the allowed names and the required * attributes (see {@link RuleClassType}). * * @throws IllegalArgumentException if an attribute with the same name exists * in more than one parent */ public Builder(String name, RuleClassType type, boolean skylark, RuleClass... parents) { this.name = name; this.skylark = skylark; this.type = type; Preconditions.checkState(skylark || type != RuleClassType.PLACEHOLDER, name); this.documented = type != RuleClassType.ABSTRACT; for (RuleClass parent : parents) { if (parent.getValidityPredicate() != PredicatesWithMessage.alwaysTrue()) { setValidityPredicate(parent.getValidityPredicate()); } if (parent.preferredDependencyPredicate != Predicates.alwaysFalse()) { setPreferredDependencyPredicate(parent.preferredDependencyPredicate); } configurationFragmentPolicy .includeConfigurationFragmentsFrom(parent.getConfigurationFragmentPolicy()); configurationFragmentPolicy.setMissingFragmentPolicy( parent.getConfigurationFragmentPolicy().getMissingFragmentPolicy()); supportsConstraintChecking = parent.supportsConstraintChecking; for (Attribute attribute : parent.getAttributes()) { String attrName = attribute.getName(); Preconditions.checkArgument( !attributes.containsKey(attrName) || attributes.get(attrName) == attribute, "Attribute %s is inherited multiple times in %s ruleclass", attrName, name); attributes.put(attrName, attribute); } advertisedProviders.addParent(parent.getAdvertisedProviders()); } // TODO(bazel-team): move this testonly attribute setting to somewhere else // preferably to some base RuleClass implementation. if (this.type.equals(RuleClassType.TEST)) { Attribute.Builder testOnlyAttr = attr("testonly", BOOLEAN).value(true) .nonconfigurable("policy decision: this shouldn't depend on the configuration"); if (attributes.containsKey("testonly")) { override(testOnlyAttr); } else { add(testOnlyAttr); } } } /** * Checks that required attributes for test rules are present, creates the * {@link RuleClass} object and returns it. * * @throws IllegalStateException if any of the required attributes is missing */ public RuleClass build() { return build(name); } /** * Same as {@link #build} except with setting the name parameter. */ public RuleClass build(String name) { Preconditions.checkArgument(this.name.isEmpty() || this.name.equals(name)); type.checkName(name); type.checkAttributes(attributes); boolean skylarkExecutable = skylark && (type == RuleClassType.NORMAL || type == RuleClassType.TEST); Preconditions.checkState( (type == RuleClassType.ABSTRACT) == (configuredTargetFactory == null && configuredTargetFunction == null)); if (!workspaceOnly) { Preconditions.checkState(skylarkExecutable == (configuredTargetFunction != null)); Preconditions.checkState(skylarkExecutable == (ruleDefinitionEnvironment != null)); Preconditions.checkState(externalBindingsFunction == NO_EXTERNAL_BINDINGS); } if (type == RuleClassType.PLACEHOLDER) { Preconditions.checkNotNull(ruleDefinitionEnvironmentHashCode, this.name); } return new RuleClass( name, skylark, skylarkExecutable, skylarkTestable, documented, publicByDefault, binaryOutput, workspaceOnly, outputsDefaultExecutable, implicitOutputsFunction, isConfigMatcher, configurator, transitionFactory, configuredTargetFactory, validityPredicate, preferredDependencyPredicate, advertisedProviders.build(), configuredTargetFunction, externalBindingsFunction, optionReferenceFunction, ruleDefinitionEnvironment, ruleDefinitionEnvironmentHashCode, configurationFragmentPolicy.build(), supportsConstraintChecking, requiredToolchains, attributes.values().toArray(new Attribute[0])); } /** * Declares that the implementation of the associated rule class requires the given * fragments to be present in this rule's host and target configurations. * *

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

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

In contrast to {@link #requiresConfigurationFragments(Class...)}, this method takes the * Skylark module names of fragments instead of their classes. */ public Builder requiresConfigurationFragmentsBySkylarkModuleName( Collection configurationFragmentNames) { configurationFragmentPolicy .requiresConfigurationFragmentsBySkylarkModuleName(configurationFragmentNames); return this; } /** * Declares the configuration fragments that are required by this rule for the host * configuration. * *

In contrast to {@link #requiresHostConfigurationFragments(Class...)}, this method takes * Skylark module names of fragments instead of their classes. */ public Builder requiresHostConfigurationFragmentsBySkylarkModuleName( Collection configurationFragmentNames) { configurationFragmentPolicy .requiresHostConfigurationFragmentsBySkylarkModuleName(configurationFragmentNames); return this; } public Builder setSkylarkTestable() { Preconditions.checkState(skylark, "Cannot set skylarkTestable on a non-Skylark rule"); skylarkTestable = true; return this; } /** * Sets the policy for the case where the configuration is missing required fragments (see * {@link #requiresConfigurationFragments}). */ public Builder setMissingFragmentPolicy(MissingFragmentPolicy missingFragmentPolicy) { configurationFragmentPolicy.setMissingFragmentPolicy(missingFragmentPolicy); return this; } public Builder setUndocumented() { documented = false; return this; } public Builder publicByDefault() { publicByDefault = true; return this; } public Builder setWorkspaceOnly() { workspaceOnly = true; return this; } /** * Determines the outputs of this rule to be created beneath the {@code * genfiles} directory. By default, files are created beneath the {@code bin} * directory. * *

This property is not inherited and this method should not be called by * builder of {@link RuleClassType#ABSTRACT} rule class. * * @throws IllegalStateException if called for abstract rule class builder */ public Builder setOutputToGenfiles() { Preconditions.checkState(type != RuleClassType.ABSTRACT, "Setting not inherited property (output to genrules) of abstract rule class '%s'", name); this.binaryOutput = false; return this; } /** * Sets the implicit outputs function of the rule class. The default implicit * outputs function is {@link ImplicitOutputsFunction#NONE}. * *

This property is not inherited and this method should not be called by * builder of {@link RuleClassType#ABSTRACT} rule class. * * @throws IllegalStateException if called for abstract rule class builder */ public Builder setImplicitOutputsFunction( ImplicitOutputsFunction implicitOutputsFunction) { Preconditions.checkState(type != RuleClassType.ABSTRACT, "Setting not inherited property (implicit output function) of abstract rule class '%s'", name); this.implicitOutputsFunction = implicitOutputsFunction; return this; } public Builder cfg(Configurator configurator) { Preconditions.checkState(type != RuleClassType.ABSTRACT, "Setting not inherited property (cfg) of abstract rule class '%s'", name); Preconditions.checkState(this.transitionFactory == null && this.configurator == NO_CHANGE, "Property cfg has already been set"); Preconditions.checkNotNull(configurator); this.configurator = configurator; return this; } /** * Applies the given transition to all incoming edges for this rule class. Does not work with * static configurations. * *

Note that the given transition must be a PatchTransition instance. We use the more * general Transtion here because PatchTransition is not available in this package. */ public Builder cfg(Transition transition) { Preconditions.checkState(type != RuleClassType.ABSTRACT, "Setting not inherited property (cfg) of abstract rule class '%s'", name); Preconditions.checkState(this.transitionFactory == null, "Property cfg has already been set"); Preconditions.checkNotNull(transition); this.transitionFactory = new FixedTransitionFactory(transition); return this; } public Builder cfg(RuleTransitionFactory transitionFactory) { Preconditions.checkState(type != RuleClassType.ABSTRACT, "Setting not inherited property (cfg) of abstract rule class '%s'", name); Preconditions.checkState(this.transitionFactory == null, "Property cfg has already been set"); Preconditions.checkNotNull(transitionFactory); this.transitionFactory = transitionFactory; return this; } public Builder factory(ConfiguredTargetFactory factory) { this.configuredTargetFactory = factory; return this; } public Builder setValidityPredicate(PredicateWithMessage predicate) { this.validityPredicate = predicate; return this; } public Builder setPreferredDependencyPredicate(Predicate predicate) { this.preferredDependencyPredicate = predicate; return this; } /** * State that the rule class being built possibly supplies the specified provider to its direct * dependencies. * *

When computing the set of aspects required for a rule, only the providers listed here are * considered. The presence of a provider here does not mean that the rule must implement * said provider, merely that it can. After the configured target is constructed from * this rule, aspects will be filtered according to the set of actual providers. * *

This is here so that we can do the loading phase overestimation required for * "blaze query", which does not have the configured targets available. * *

It's okay for the rule class eventually not to supply it (possibly based on analysis phase * logic), but if a provider is not advertised but is supplied, aspects that require the it will * not be evaluated for the rule. */ public Builder advertiseProvider(Class... providers) { for (Class provider : providers) { advertisedProviders.addNative(provider); } return this; } /** * Set if the rule can have any provider. This is true for "alias" rules like * bind . */ public Builder canHaveAnyProvider() { advertisedProviders.canHaveAnyProvider(); return this; } private void addAttribute(Attribute attribute) { Preconditions.checkState(!attributes.containsKey(attribute.getName()), "An attribute with the name '%s' already exists.", attribute.getName()); attributes.put(attribute.getName(), attribute); } private void overrideAttribute(Attribute attribute) { String attrName = attribute.getName(); Preconditions.checkState(attributes.containsKey(attrName), "No such attribute '%s' to override in ruleclass '%s'.", attrName, name); Type origType = attributes.get(attrName).getType(); Type newType = attribute.getType(); Preconditions.checkState(origType.equals(newType), "The type of the new attribute '%s' is different from the original one '%s'.", newType, origType); attributes.put(attrName, attribute); } /** * Builds attribute from the attribute builder and adds it to this rule * class. * * @param attr attribute builder */ public Builder add(Attribute.Builder attr) { addAttribute(attr.build()); return this; } /** * Builds attribute from the attribute builder and overrides the attribute * with the same name. * * @throws IllegalArgumentException if the attribute does not override one of the same name */ public Builder override(Attribute.Builder attr) { overrideAttribute(attr.build()); return this; } /** * Adds or overrides the attribute in the rule class. Meant for Skylark usage. * * @throws IllegalArgumentException if the attribute overrides an existing attribute (will be * legal in the future). */ public void addOrOverrideAttribute(Attribute attribute) { String name = attribute.getName(); // Attributes may be overridden in the future. Preconditions.checkArgument(!attributes.containsKey(name), "There is already a built-in attribute '%s' which cannot be overridden", name); addAttribute(attribute); } /** True if the rule class contains an attribute named {@code name}. */ public boolean contains(String name) { return attributes.containsKey(name); } /** * Sets the rule implementation function. Meant for Skylark usage. */ public Builder setConfiguredTargetFunction(BaseFunction func) { this.configuredTargetFunction = func; return this; } public Builder setExternalBindingsFunction(Function> func) { this.externalBindingsFunction = func; return this; } /** Sets the rule definition environment. Meant for Skylark usage. */ public Builder setRuleDefinitionEnvironment(Environment env) { this.ruleDefinitionEnvironment = Preconditions.checkNotNull(env, this.name); this.ruleDefinitionEnvironmentHashCode = this.ruleDefinitionEnvironment.getTransitiveContentHashCode(); return this; } /** Sets the rule definition environment hash code for deserialized rule classes. */ Builder setRuleDefinitionEnvironmentHashCode(String hashCode) { Preconditions.checkState(ruleDefinitionEnvironment == null, ruleDefinitionEnvironment); Preconditions.checkState(type == RuleClassType.PLACEHOLDER, type); this.ruleDefinitionEnvironmentHashCode = Preconditions.checkNotNull(hashCode, this.name); return this; } /** * Removes an attribute with the same name from this rule class. * * @throws IllegalArgumentException if the attribute with this name does * not exist */ public Builder removeAttribute(String name) { Preconditions.checkState(attributes.containsKey(name), "No such attribute '%s' to remove.", name); attributes.remove(name); return this; } /** * This rule class outputs a default executable for every rule with the same name as * the rules's. Only works for Skylark. */ public Builder setOutputsDefaultExecutable() { this.outputsDefaultExecutable = true; return this; } /** * Declares that instances of this rule are compatible with the specified environments, * in addition to the defaults declared by their environment groups. This can be overridden * by rule-specific declarations. See * {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics} for details. */ public Builder compatibleWith(Label... environments) { add(attr(DEFAULT_COMPATIBLE_ENVIRONMENT_ATTR, LABEL_LIST).cfg(HOST) .value(ImmutableList.copyOf(environments))); return this; } /** * Declares that instances of this rule are restricted to the specified environments, i.e. * these override the defaults declared by their environment groups. This can be overridden * by rule-specific declarations. See * {@link com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics} for details. * *

The input list cannot be empty. */ public Builder restrictedTo(Label firstEnvironment, Label... otherEnvironments) { ImmutableList

This is only intended for use by {@code config_setting} - other rules should not use this! */ public Builder setIsConfigMatcherForConfigSettingOnly() { this.isConfigMatcher = true; return this; } /** * Causes rules of this type to implicitly reference the configuration fragments associated with * the options its attributes reference. * *

This is only intended for use by {@code config_setting} - other rules should not use this! */ public Builder setOptionReferenceFunctionForConfigSettingOnly( Function> optionReferenceFunction) { this.optionReferenceFunction = Preconditions.checkNotNull(optionReferenceFunction); return this; } public Builder addRequiredToolchain(ClassObjectConstructor.Key toolchain) { this.requiredToolchains.add(toolchain); return this; } /** * Returns an Attribute.Builder object which contains a replica of the * same attribute in the parent rule if exists. * * @param name the name of the attribute */ public Attribute.Builder copy(String name) { Preconditions.checkArgument(attributes.containsKey(name), "Attribute %s does not exist in parent rule class.", name); return attributes.get(name).cloneBuilder(); } } private final String name; // e.g. "cc_library" /** * The kind of target represented by this RuleClass (e.g. "cc_library rule"). * Note: Even though there is partial duplication with the {@link RuleClass#name} field, * we want to store this as a separate field instead of generating it on demand in order to * avoid string duplication. */ private final String targetKind; private final boolean isSkylark; private final boolean skylarkExecutable; private final boolean skylarkTestable; private final boolean documented; private final boolean publicByDefault; private final boolean binaryOutput; private final boolean workspaceOnly; private final boolean outputsDefaultExecutable; private final boolean isConfigMatcher; /** * A (unordered) mapping from attribute names to small integers indexing into * the {@code attributes} array. */ private final Map attributeIndex; /** * All attributes of this rule class (including inherited ones) ordered by * attributeIndex value. */ private final ImmutableList attributes; /** Names of the non-configurable attributes of this rule class. */ private final ImmutableList nonConfigurableAttributes; /** * The set of implicit outputs generated by a rule, expressed as a function * of that rule. */ private final ImplicitOutputsFunction implicitOutputsFunction; /** * The set of implicit outputs generated by a rule, expressed as a function * of that rule. */ private final Configurator configurator; /** * A factory which will produce a configuration transition that should be applied on any edge of * the configured target graph that leads into a target of this rule class. */ private final RuleTransitionFactory transitionFactory; /** * The factory that creates configured targets from this rule. */ private final ConfiguredTargetFactory configuredTargetFactory; /** * The constraint the package name of the rule instance must fulfill */ private final PredicateWithMessage validityPredicate; /** * See {@link #isPreferredDependency}. */ private final Predicate preferredDependencyPredicate; /** * The list of transitive info providers this class advertises to aspects. */ private final AdvertisedProviderSet advertisedProviders; /** * The Skylark rule implementation of this RuleClass. Null for non Skylark executable RuleClasses. */ @Nullable private final BaseFunction configuredTargetFunction; /** * Returns the extra bindings a workspace function adds to the WORKSPACE file. */ private final Function> externalBindingsFunction; /** * Returns the options referenced by this rule's attributes. */ private final Function> optionReferenceFunction; /** * The Skylark rule definition environment of this RuleClass. * Null for non Skylark executable RuleClasses. */ @Nullable private final Environment ruleDefinitionEnvironment; @Nullable private final String ruleDefinitionEnvironmentHashCode; /** * The set of configuration fragments which are legal for this rule's implementation to access. */ private final ConfigurationFragmentPolicy configurationFragmentPolicy; /** * Determines whether instances of this rule should be checked for constraint compatibility * with their dependencies and the rules that depend on them. This should be true for * everything except for rules that are intrinsically incompatible with the constraint system. */ private final boolean supportsConstraintChecking; private final ImmutableList requiredToolchains; /** * Constructs an instance of RuleClass whose name is 'name', attributes are 'attributes'. The * {@code srcsAllowedFiles} determines which types of files are allowed as parameters to the * "srcs" attribute; rules are always allowed. For the "deps" attribute, there are four cases: * *

    *
  • if the parameter is a file, it is allowed if its file type is given in {@code * depsAllowedFiles}, *
  • if the parameter is a rule and the rule class is accepted by {@code depsAllowedRules}, * then it is allowed, *
  • if the parameter is a rule and the rule class is not accepted by {@code * depsAllowedRules}, but accepted by {@code depsAllowedRulesWithWarning}, then it is * allowed, but triggers a warning; *
  • all other parameters trigger an error. *
* *

The {@code depsAllowedRules} predicate should have a {@code toString} method which returns a * plain English enumeration of the allowed rule class names, if it does not allow all rule * classes. */ @VisibleForTesting RuleClass( String name, boolean isSkylark, boolean skylarkExecutable, boolean skylarkTestable, boolean documented, boolean publicByDefault, boolean binaryOutput, boolean workspaceOnly, boolean outputsDefaultExecutable, ImplicitOutputsFunction implicitOutputsFunction, boolean isConfigMatcher, Configurator configurator, RuleTransitionFactory transitionFactory, ConfiguredTargetFactory configuredTargetFactory, PredicateWithMessage validityPredicate, Predicate preferredDependencyPredicate, AdvertisedProviderSet advertisedProviders, @Nullable BaseFunction configuredTargetFunction, Function> externalBindingsFunction, Function> optionReferenceFunction, @Nullable Environment ruleDefinitionEnvironment, String ruleDefinitionEnvironmentHashCode, ConfigurationFragmentPolicy configurationFragmentPolicy, boolean supportsConstraintChecking, List requiredToolchains, Attribute... attributes) { this.name = name; this.isSkylark = isSkylark; this.targetKind = name + Rule.targetKindSuffix(); this.skylarkExecutable = skylarkExecutable; this.skylarkTestable = skylarkTestable; this.documented = documented; this.publicByDefault = publicByDefault; this.binaryOutput = binaryOutput; this.implicitOutputsFunction = implicitOutputsFunction; this.isConfigMatcher = isConfigMatcher; this.configurator = Preconditions.checkNotNull(configurator); this.transitionFactory = transitionFactory; this.configuredTargetFactory = configuredTargetFactory; this.validityPredicate = validityPredicate; this.preferredDependencyPredicate = preferredDependencyPredicate; this.advertisedProviders = advertisedProviders; this.configuredTargetFunction = configuredTargetFunction; this.externalBindingsFunction = externalBindingsFunction; this.optionReferenceFunction = optionReferenceFunction; this.ruleDefinitionEnvironment = ruleDefinitionEnvironment; this.ruleDefinitionEnvironmentHashCode = ruleDefinitionEnvironmentHashCode; validateNoClashInPublicNames(attributes); this.attributes = ImmutableList.copyOf(attributes); this.workspaceOnly = workspaceOnly; this.outputsDefaultExecutable = outputsDefaultExecutable; this.configurationFragmentPolicy = configurationFragmentPolicy; this.supportsConstraintChecking = supportsConstraintChecking; this.requiredToolchains = ImmutableList.copyOf(requiredToolchains); // Create the index and collect non-configurable attributes. int index = 0; attributeIndex = new HashMap<>(attributes.length); ImmutableList.Builder nonConfigurableAttributesBuilder = ImmutableList.builder(); for (Attribute attribute : attributes) { attributeIndex.put(attribute.getName(), index++); if (!attribute.isConfigurable()) { nonConfigurableAttributesBuilder.add(attribute.getName()); } } this.nonConfigurableAttributes = nonConfigurableAttributesBuilder.build(); } private void validateNoClashInPublicNames(Attribute[] attributes) { Map publicToPrivateNames = new HashMap<>(); for (Attribute attribute : attributes) { String publicName = attribute.getPublicName(); if (publicToPrivateNames.containsKey(publicName)) { throw new IllegalStateException( String.format( "Rule %s: Attributes %s and %s have an identical public name: %s", name, attribute.getName(), publicToPrivateNames.get(publicName).getName(), publicName)); } publicToPrivateNames.put(publicName, attribute); } } /** * Returns the default function for determining the set of implicit outputs generated by a given * rule. If not otherwise specified, this will be the implementation used by {@link Rule}s * created with this {@link RuleClass}. * *

Do not use this value to calculate implicit outputs for a rule, instead use * {@link Rule#getImplicitOutputsFunction()}. * *

An implicit output is an OutputFile that automatically comes into existence when a rule of * this class is declared, and whose name is derived from the name of the rule. * *

Implicit outputs are a widely-relied upon. All ".so", and "_deploy.jar" targets referenced * in BUILD files are examples. */ @VisibleForTesting public ImplicitOutputsFunction getDefaultImplicitOutputsFunction() { return implicitOutputsFunction; } @SuppressWarnings("unchecked") public Configurator getConfigurator() { return (Configurator) configurator; } public RuleTransitionFactory getTransitionFactory() { return transitionFactory; } @SuppressWarnings("unchecked") public ConfiguredTargetFactory getConfiguredTargetFactory() { return (ConfiguredTargetFactory) configuredTargetFactory; } /** * Returns the class of rule that this RuleClass represents (e.g. "cc_library"). */ public String getName() { return name; } /** * Returns the target kind of this class of rule (e.g. "cc_library rule"). */ String getTargetKind() { return targetKind; } public boolean getWorkspaceOnly() { return workspaceOnly; } /** * Returns true iff the attribute 'attrName' is defined for this rule class, * and has type 'type'. */ public boolean hasAttr(String attrName, Type type) { Integer index = getAttributeIndex(attrName); return index != null && getAttribute(index).getType() == type; } /** * Returns the index of the specified attribute name. Use of indices allows * space-efficient storage of attribute values in rules, since hashtables are * not required. (The index mapping is specific to each RuleClass and an * attribute may have a different index in the parent RuleClass.) * *

Returns null if the named attribute is not defined for this class of Rule. */ Integer getAttributeIndex(String attrName) { return attributeIndex.get(attrName); } /** * Returns the attribute whose index is 'attrIndex'. Fails if attrIndex is * not in range. */ Attribute getAttribute(int attrIndex) { return attributes.get(attrIndex); } /** * Returns the attribute whose name is 'attrName'; fails with NullPointerException if not found. */ public Attribute getAttributeByName(String attrName) { Integer attrIndex = Preconditions.checkNotNull(getAttributeIndex(attrName), "Attribute %s does not exist", attrName); return attributes.get(attrIndex); } /** * Returns the attribute whose name is {@code attrName}, or null if not * found. */ Attribute getAttributeByNameMaybe(String attrName) { Integer i = getAttributeIndex(attrName); return i == null ? null : attributes.get(i); } /** * Returns the number of attributes defined for this rule class. */ public int getAttributeCount() { return attributeIndex.size(); } /** * Returns an (immutable) list of all Attributes defined for this class of * rule, ordered by increasing index. */ public List getAttributes() { return attributes; } /** Returns set of non-configurable attribute names defined for this class of rule. */ public List getNonConfigurableAttributes() { return nonConfigurableAttributes; } public PredicateWithMessage getValidityPredicate() { return validityPredicate; } /** * Returns the set of advertised transitive info providers. * *

When computing the set of aspects required for a rule, only the providers listed here are * considered. The presence of a provider here does not mean that the rule must implement * said provider, merely that it can. After the configured target is constructed from this * rule, aspects will be filtered according to the set of actual providers. * *

This is here so that we can do the loading phase overestimation required for "blaze query", * which does not have the configured targets available. **/ public AdvertisedProviderSet getAdvertisedProviders() { return advertisedProviders; } /** * For --compile_one_dependency: if multiple rules consume the specified target, * should we choose this one over the "unpreferred" options? */ public boolean isPreferredDependency(String filename) { return preferredDependencyPredicate.apply(filename); } /** * Returns this rule's policy for configuration fragment access. */ public ConfigurationFragmentPolicy getConfigurationFragmentPolicy() { return configurationFragmentPolicy; } /** * Returns true if rules of this type can be used with the constraint enforcement system. */ public boolean supportsConstraintChecking() { return supportsConstraintChecking; } /** * Returns true if rules of this type should be evaluated with the parent's configuration so that * they can match on aspects of it. */ public boolean isConfigMatcher() { return isConfigMatcher; } /** * Creates a new {@link Rule} {@code r} where {@code r.getPackage()} is the {@link Package} * associated with {@code pkgBuilder}. * *

The created {@link Rule} will be populated with attribute values from {@code * attributeValues} or the default attribute values associated with this {@link RuleClass} and * {@code pkgBuilder}. * *

The created {@link Rule} will also be populated with output files. These output files will * have been collected from the explicitly provided values of type {@link BuildType#OUTPUT} and * {@link BuildType#OUTPUT_LIST} as well as from the implicit outputs determined by this {@link * RuleClass} and the values in {@code attributeValues}. * *

This performs several validity checks. Invalid output file labels result in a thrown {@link * LabelSyntaxException}. Computed default attributes that fail during precomputation result in a * {@link CannotPrecomputeDefaultsException}. All other errors are reported on {@code * eventHandler}. */ Rule createRule( Package.Builder pkgBuilder, Label ruleLabel, AttributeValuesMap attributeValues, EventHandler eventHandler, @Nullable FuncallExpression ast, Location location, AttributeContainer attributeContainer) throws LabelSyntaxException, InterruptedException, CannotPrecomputeDefaultsException { Rule rule = pkgBuilder.createRule(ruleLabel, this, location, attributeContainer); populateRuleAttributeValues(rule, pkgBuilder, attributeValues, eventHandler); checkAspectAllowedValues(rule, eventHandler); rule.populateOutputFiles(eventHandler, pkgBuilder); if (ast != null) { populateAttributeLocations(rule, ast); } checkForDuplicateLabels(rule, eventHandler); checkThirdPartyRuleHasLicense(rule, pkgBuilder, eventHandler); checkForValidSizeAndTimeoutValues(rule, eventHandler); rule.checkValidityPredicate(eventHandler); rule.checkForNullLabels(); return rule; } /** * Same as {@link #createRule}, except without some internal sanity checks. * *

Don't call this function unless you know what you're doing. */ Rule createRuleUnchecked( Package.Builder pkgBuilder, Label ruleLabel, AttributeValuesMap attributeValues, Location location, AttributeContainer attributeContainer, ImplicitOutputsFunction implicitOutputsFunction) throws LabelSyntaxException, InterruptedException, CannotPrecomputeDefaultsException { Rule rule = pkgBuilder.createRule( ruleLabel, this, location, attributeContainer, implicitOutputsFunction); populateRuleAttributeValues(rule, pkgBuilder, attributeValues, NullEventHandler.INSTANCE); rule.populateOutputFiles(NullEventHandler.INSTANCE, pkgBuilder); return rule; } /** * Populates the attributes table of the new {@link Rule} with the values in the {@code * attributeValues} map and with default values provided by this {@link RuleClass} and the {@code * pkgBuilder}. * *

Errors are reported on {@code eventHandler}. */ private void populateRuleAttributeValues( Rule rule, Package.Builder pkgBuilder, AttributeValuesMap attributeValues, EventHandler eventHandler) throws InterruptedException, CannotPrecomputeDefaultsException { BitSet definedAttrIndices = populateDefinedRuleAttributeValues(rule, attributeValues, eventHandler); populateDefaultRuleAttributeValues(rule, pkgBuilder, definedAttrIndices, eventHandler); // Now that all attributes are bound to values, collect and store configurable attribute keys. populateConfigDependenciesAttribute(rule); } /** * Populates the attributes table of the new {@link Rule} with the values in the {@code * attributeValues} map. * *

Handles the special cases of the attribute named {@code "name"} and attributes with value * {@link Runtime#NONE}. * *

Returns a bitset {@code b} where {@code b.get(i)} is {@code true} if this method set a * value for the attribute with index {@code i} in this {@link RuleClass}. Errors are reported * on {@code eventHandler}. */ private BitSet populateDefinedRuleAttributeValues( Rule rule, AttributeValuesMap attributeValues, EventHandler eventHandler) { BitSet definedAttrIndices = new BitSet(); for (String attributeName : attributeValues.getAttributeNames()) { Object attributeValue = attributeValues.getAttributeValue(attributeName); // Ignore all None values. if (attributeValue == Runtime.NONE) { continue; } // Check that the attribute's name belongs to a valid attribute for this rule class. Integer attrIndex = getAttributeIndex(attributeName); if (attrIndex == null) { rule.reportError( String.format( "%s: no such attribute '%s' in '%s' rule", rule.getLabel(), attributeName, name), eventHandler); continue; } Attribute attr = getAttribute(attrIndex); // Convert the build-lang value to a native value, if necessary. Object nativeAttributeValue; if (attributeValues.valuesAreBuildLanguageTyped()) { try { nativeAttributeValue = convertFromBuildLangType(rule, attr, attributeValue); } catch (ConversionException e) { rule.reportError(String.format("%s: %s", rule.getLabel(), e.getMessage()), eventHandler); continue; } } else { nativeAttributeValue = attributeValue; } boolean explicit = attributeValues.isAttributeExplicitlySpecified(attributeName); setRuleAttributeValue(rule, eventHandler, attr, nativeAttributeValue, explicit); definedAttrIndices.set(attrIndex); } return definedAttrIndices; } /** Populates attribute locations for attributes defined in {@code ast}. */ private void populateAttributeLocations(Rule rule, FuncallExpression ast) { for (Argument.Passed arg : ast.getArguments()) { if (arg.isKeyword()) { String name = arg.getName(); Integer attrIndex = getAttributeIndex(name); if (attrIndex != null) { rule.setAttributeLocation(attrIndex, arg.getValue().getLocation()); } } } } /** * Populates the attributes table of the new {@link Rule} with default values provided by this * {@link RuleClass} and the {@code pkgBuilder}. This will only provide values for attributes that * haven't already been populated, using {@code definedAttrIndices} to determine whether an * attribute was populated. * *

Errors are reported on {@code eventHandler}. */ private void populateDefaultRuleAttributeValues( Rule rule, Package.Builder pkgBuilder, BitSet definedAttrIndices, EventHandler eventHandler) throws InterruptedException, CannotPrecomputeDefaultsException { // Set defaults; ensure that every mandatory attribute has a value. Use the default if none // is specified. List attrsWithComputedDefaults = new ArrayList<>(); int numAttributes = getAttributeCount(); for (int attrIndex = 0; attrIndex < numAttributes; ++attrIndex) { if (definedAttrIndices.get(attrIndex)) { continue; } Attribute attr = getAttribute(attrIndex); if (attr.isMandatory()) { rule.reportError( String.format( "%s: missing value for mandatory attribute '%s' in '%s' rule", rule.getLabel(), attr.getName(), name), eventHandler); } if (attr.hasComputedDefault()) { // Note that it is necessary to set all non-computed default values before calling // Attribute#getDefaultValue for computed default attributes. Computed default attributes // may have a condition predicate (i.e. the predicate returned by Attribute#getCondition) // that depends on non-computed default attribute values, and that condition predicate is // evaluated by the call to Attribute#getDefaultValue. attrsWithComputedDefaults.add(attr); } else { Object defaultValue = getAttributeNoncomputedDefaultValue(attr, pkgBuilder); rule.setAttributeValue(attr, defaultValue, /*explicit=*/ false); checkAllowedValues(rule, attr, eventHandler); } } // Set computed default attribute values now that all other (i.e. non-computed) default values // have been set. for (Attribute attr : attrsWithComputedDefaults) { // If Attribute#hasComputedDefault was true above, Attribute#getDefaultValue returns the // computed default function object or a Skylark computed default template. Note that we // cannot determine the exact value of the computed default function here because it may // depend on other attribute values that are configurable (i.e. they came from select({..}) // expressions in the build language, and they require configuration data from the analysis // phase to be resolved). Instead, we're setting the attribute value to a reference to the // computed default function, or if #getDefaultValue is a Skylark computed default // template, setting the attribute value to a reference to the SkylarkComputedDefault // returned from SkylarkComputedDefaultTemplate#computePossibleValues. // // SkylarkComputedDefaultTemplate#computePossibleValues pre-computes all possible values the // function may evaluate to, and records them in a lookup table. By calling it here, with an // EventHandler, any errors that might occur during the function's evaluation can // be discovered and propagated here. Object valueToSet; Object defaultValue = attr.getDefaultValue(rule); if (defaultValue instanceof SkylarkComputedDefaultTemplate) { SkylarkComputedDefaultTemplate template = (SkylarkComputedDefaultTemplate) defaultValue; valueToSet = template.computePossibleValues(attr, rule, eventHandler); } else { valueToSet = defaultValue; } rule.setAttributeValue(attr, valueToSet, /*explicit=*/ false); } } /** * Collects all labels used as keys for configurable attributes and places them into * the special implicit attribute that tracks them. */ private static void populateConfigDependenciesAttribute(Rule rule) { RawAttributeMapper attributes = RawAttributeMapper.of(rule); Attribute configDepsAttribute = attributes.getAttributeDefinition("$config_dependencies"); if (configDepsAttribute == null) { // Not currently compatible with Skylark rules. return; } Set

For most rule attributes, the default value is either explicitly specified * in the attribute, or implicitly based on the type of the attribute, except * for some special cases (e.g. "licenses", "distribs") where it comes from * some other source, such as state in the package. * *

Precondition: {@code !attr.hasComputedDefault()}. (Computed defaults are * evaluated in second pass.) */ private static Object getAttributeNoncomputedDefaultValue(Attribute attr, Package.Builder pkgBuilder) { if (attr.getName().equals("licenses")) { return pkgBuilder.getDefaultLicense(); } if (attr.getName().equals("distribs")) { return pkgBuilder.getDefaultDistribs(); } return attr.getDefaultValue(null); } /** * Sets the value of attribute {@code attr} in {@code rule} to the native value {@code * nativeAttrVal}, and sets the value's explicitness to {@code explicit}. * *

Handles the special case of the "visibility" attribute by also setting the rule's * visibility with {@link Rule#setVisibility}. * *

Checks that {@code nativeAttrVal} is an allowed value via {@link #checkAllowedValues}. */ private static void setRuleAttributeValue( Rule rule, EventHandler eventHandler, Attribute attr, Object nativeAttrVal, boolean explicit) { if (attr.getName().equals("visibility")) { @SuppressWarnings("unchecked") List

Throws {@link ConversionException} if the conversion fails, or if {@code buildLangValue} * is a selector expression but {@code attr.isConfigurable()} is {@code false}. */ private static Object convertFromBuildLangType(Rule rule, Attribute attr, Object buildLangValue) throws ConversionException { Object converted = BuildType.selectableConvert( attr.getType(), buildLangValue, new AttributeConversionContext(attr.getName(), rule.getRuleClass()), rule.getLabel()); if ((converted instanceof SelectorList) && !attr.isConfigurable()) { throw new ConversionException( String.format("attribute \"%s\" is not configurable", attr.getName())); } if ((converted instanceof List) && !(converted instanceof GlobList)) { if (attr.isOrderIndependent()) { @SuppressWarnings("unchecked") List> list = (List>) converted; converted = Ordering.natural().sortedCopy(list); } converted = ImmutableList.copyOf((List) converted); } return converted; } /** * Provides a {@link #toString()} description of the attribute being converted for * {@link BuildType#selectableConvert}. This is preferred over a raw string to avoid uselessly * constructing strings which are never used. A separate class instead of inline to avoid * accidental memory leaks. */ private static class AttributeConversionContext { private final String attrName; private final String ruleClass; AttributeConversionContext(String attrName, String ruleClass) { this.attrName = attrName; this.ruleClass = ruleClass; } @Override public String toString() { return "attribute '" + attrName + "' in '" + ruleClass + "' rule"; } } /** * Verifies that the rule has a valid value for the attribute according to its allowed values. * *

If the value for the given attribute on the given rule is invalid, an error will be recorded * in the given EventHandler. * *

If the rule is configurable, all of its potential values are evaluated, and errors for each * of the invalid values are reported. */ private static void checkAllowedValues( Rule rule, Attribute attribute, EventHandler eventHandler) { if (attribute.checkAllowedValues()) { PredicateWithMessage allowedValues = attribute.getAllowedValues(); Iterable values = AggregatingAttributeMapper.of(rule).visitAttribute( attribute.getName(), attribute.getType()); for (Object value : values) { if (!allowedValues.apply(value)) { rule.reportError( String.format( "%s: invalid value in '%s' attribute: %s", rule.getLabel(), attribute.getName(), allowedValues.getErrorReason(value)), eventHandler); } } } } private static void checkAspectAllowedValues( Rule rule, EventHandler eventHandler) { for (Attribute attrOfRule : rule.getAttributes()) { for (Aspect aspect : attrOfRule.getAspects(rule)) { for (Attribute attrOfAspect : aspect.getDefinition().getAttributes().values()) { // By this point the AspectDefinition has been created and values assigned. if (attrOfAspect.checkAllowedValues()) { PredicateWithMessage allowedValues = attrOfAspect.getAllowedValues(); Object value = attrOfAspect.getDefaultValue(rule); if (!allowedValues.apply(value)) { rule.reportError( String.format( "%s: invalid value in '%s' attribute: %s", rule.getLabel(), attrOfAspect.getName(), allowedValues.getErrorReason(value)), eventHandler); } } } } } } @Override public String toString() { return name; } public boolean isDocumented() { return documented; } public boolean isPublicByDefault() { return publicByDefault; } /** * Returns true iff the outputs of this rule should be created beneath the * bin directory, false if beneath genfiles. For most rule * classes, this is a constant, but for genrule, it is a property of the * individual rule instance, derived from the 'output_to_bindir' attribute; * see Rule.hasBinaryOutput(). */ @VisibleForTesting public boolean hasBinaryOutput() { return binaryOutput; } /** * Returns this RuleClass's custom Skylark rule implementation. */ @Nullable public BaseFunction getConfiguredTargetFunction() { return configuredTargetFunction; } /** * Returns a function that computes the external bindings a repository function contributes to * the WORKSPACE file. */ public Function> getExternalBindingsFunction() { return externalBindingsFunction; } /** * Returns a function that computes the options referenced by a rule. */ public Function> getOptionReferenceFunction() { return optionReferenceFunction; } /** * Returns this RuleClass's rule definition environment. Is null for native rules' RuleClass * objects and deserialized Skylark rules. Deserialized rules do provide a hash code encapsulating * their behavior, available at {@link #getRuleDefinitionEnvironmentHashCode}. */ @Nullable public Environment getRuleDefinitionEnvironment() { return ruleDefinitionEnvironment; } /** * Returns the hash code for the RuleClass's rule definition environment. In deserialization, * this RuleClass may not actually contain its environment, in which case the hash code is all * that is available. Will be null for native rules' RuleClass objects. */ @Nullable public String getRuleDefinitionEnvironmentHashCode() { return ruleDefinitionEnvironmentHashCode; } /** Returns true if this RuleClass is a Skylark-defined RuleClass. */ public boolean isSkylark() { return isSkylark; } /** * Returns true if this RuleClass is an executable Skylark RuleClass (i.e. it is * Skylark and Normal or Test RuleClass). */ public boolean isSkylarkExecutable() { return skylarkExecutable; } /** * Returns true if this RuleClass is Skylark-defined and is subject to analysis-time * tests. */ public boolean isSkylarkTestable() { return skylarkTestable; } /** * Returns true if this rule class outputs a default executable for every rule. */ public boolean outputsDefaultExecutable() { return outputsDefaultExecutable; } public ImmutableList getRequiredToolchains() { return requiredToolchains; } public static boolean isThirdPartyPackage(PackageIdentifier packageIdentifier) { if (!packageIdentifier.getRepository().isMain()) { return false; } if (!packageIdentifier.getPackageFragment().startsWith(THIRD_PARTY_PREFIX)) { return false; } if (packageIdentifier.getPackageFragment().segmentCount() <= 1) { return false; } return true; } }