blob: 91723d0810e6978b21b335da4e476dd8d117d046 [file] [log] [blame]
// Copyright 2014 Google Inc. 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.Type.BOOLEAN;
import static com.google.devtools.build.lib.packages.Type.LABEL_LIST;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Preconditions;
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.events.EventHandler;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
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.FragmentClassNameResolver;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.GlobList;
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Label.SyntaxException;
import com.google.devtools.build.lib.syntax.Runtime;
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.Collections;
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
* <code>cc_binary</code>.
*
* <p>This is an instance of the "meta-class" pattern for Rules: we achieve using <i>values</i>
* what subclasses achieve using <i>types</i>. (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.)
*
* <p>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.
*
* <p>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.
*
* <p>The exact values in this class are important. In particular:
* <ul>
* <li>Changing an attribute from MANDATORY to OPTIONAL creates the potential for null-pointer
* exceptions in code that expects a value.
* <li>Attributes whose names are preceded by a "$" or a ":" are "hidden", and cannot be redefined
* in a BUILD file. They are a useful way of adding a special dependency. By convention,
* attributes starting with "$" are implicit dependencies, and those starting with a ":" are
* late-bound implicit dependencies, i.e. dependencies that can only be resolved when the
* configuration is known.
* <li>Attributes should not be introduced into the hierarchy higher then necessary.
* <li>The 'deps' and 'data' attributes are treated specially by the code that builds the runfiles
* tree. All targets appearing in these attributes appears beneath the ".runfiles" tree; in
* addition, "deps" may have rule-specific semantics.
* </ul>
*/
@Immutable
public final class RuleClass {
public static final Function<? super Rule, Map<String, Label>> NO_EXTERNAL_BINDINGS =
Functions.<Map<String, Label>>constant(ImmutableMap.<String, Label>of());
/**
* A constraint for the package name of the Rule instances.
*/
public static class PackageNameConstraint implements PredicateWithMessage<Rule> {
public static final int ANY_SEGMENT = 0;
private final int pathSegment;
private final Set<String> 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<String> getValues() {
return values;
}
}
/**
* Using this callback function, rules can override their own configuration during the
* analysis phase.
*/
public interface Configurator<TConfig, TRule> {
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<TConfiguredTarget, TContext> {
/**
* Returns a fully initialized configured target instance using the given context.
*/
TConfiguredTarget create(TContext ruleContext) throws InterruptedException;
}
/**
* Default rule configurator, it doesn't change the assigned configuration.
*/
public static final RuleClass.Configurator<Object, Object> NO_CHANGE =
new RuleClass.Configurator<Object, Object>() {
@Override
public Object apply(Object rule, Object configuration) {
return configuration;
}
@Override
public String getCategory() {
return "core";
}
};
/**
* How to handle the case if the configuration is missing fragments that are required according
* to the rule class.
*/
public enum MissingFragmentPolicy {
/**
* Some rules are monolithic across languages, and we want them to continue to work even when
* individual languages are disabled. Use this policy if the rule implementation is handling
* missing fragments.
*/
IGNORE,
/**
* Use this policy to generate fail actions for the target rather than failing the analysis
* outright. Again, this is used when rules are monolithic across languages, but we still need
* to analyze the dependent libraries. (Instead of this mechanism, consider annotating
* attributes as unused if certain fragments are unavailable.)
*/
CREATE_FAIL_ACTIONS,
/**
* Use this policy to fail the analysis of that target with an error message; this is the
* default.
*/
FAIL_ANALYSIS;
}
/**
* 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;
/**
* Checks if an attribute is part of the constraint system.
*/
public static boolean isConstraintAttribute(String attr) {
return RESTRICTED_ENVIRONMENT_ATTR.equals(attr)
|| COMPATIBLE_ENVIRONMENT_ATTR.equals(attr)
|| DEFAULT_RESTRICTED_ENVIRONMENT_ATTR.equals(attr)
|| DEFAULT_COMPATIBLE_ENVIRONMENT_ATTR.equals(attr);
}
/**
* A support class to make it easier to create {@code RuleClass} instances.
* This class follows the 'fluent builder' pattern.
*
* <p>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.
*
* <p>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<String, Attribute> 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<String, Attribute> 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<String, Attribute> 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<String, Attribute> 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.
*
* <p>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<String, Attribute> 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<String, Attribute> 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<String, Attribute> attributes);
}
/**
* A predicate that filters rule classes based on their names.
*/
public static class RuleClassNamePredicate implements Predicate<RuleClass> {
private final Set<String> ruleClasses;
public RuleClassNamePredicate(Iterable<String> ruleClasses) {
this.ruleClasses = ImmutableSet.copyOf(ruleClasses);
}
public RuleClassNamePredicate(String... ruleClasses) {
this.ruleClasses = ImmutableSet.copyOf(ruleClasses);
}
public RuleClassNamePredicate() {
this(ImmutableSet.<String>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);
}
}
/**
* List of required attributes for normal rules, name and type.
*/
public static final List<Attribute> 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 List<Attribute> 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 documented;
private boolean publicByDefault = false;
private boolean binaryOutput = true;
private boolean workspaceOnly = false;
private boolean outputsDefaultExecutable = false;
private ImplicitOutputsFunction implicitOutputsFunction = ImplicitOutputsFunction.NONE;
private Configurator<?, ?> configurator = NO_CHANGE;
private ConfiguredTargetFactory<?, ?> configuredTargetFactory = null;
private PredicateWithMessage<Rule> validityPredicate =
PredicatesWithMessage.<Rule>alwaysTrue();
private Predicate<String> preferredDependencyPredicate = Predicates.alwaysFalse();
private List<Class<?>> advertisedProviders = new ArrayList<>();
private BaseFunction configuredTargetFunction = null;
private Function<? super Rule, Map<String, Label>> externalBindingsFunction =
NO_EXTERNAL_BINDINGS;
private Environment ruleDefinitionEnvironment = null;
private Set<Class<?>> configurationFragments = new LinkedHashSet<>();
private MissingFragmentPolicy missingFragmentPolicy = MissingFragmentPolicy.FAIL_ANALYSIS;
private Map<ConfigurationTransition, ImmutableSet<String>> requiredFragmentNames =
new LinkedHashMap<>();
private FragmentClassNameResolver fragmentNameResolver;
private boolean supportsConstraintChecking = true;
private final Map<String, Attribute> attributes = new LinkedHashMap<>();
/**
* Constructs a new {@code RuleClassBuilder} using all attributes from all
* parent rule classes. An attribute cannot exist in more than one parent.
*
* <p>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;
this.documented = type != RuleClassType.ABSTRACT;
for (RuleClass parent : parents) {
if (parent.getValidityPredicate() != PredicatesWithMessage.<Rule>alwaysTrue()) {
setValidityPredicate(parent.getValidityPredicate());
}
if (parent.preferredDependencyPredicate != Predicates.<String>alwaysFalse()) {
setPreferredDependencyPredicate(parent.preferredDependencyPredicate);
}
configurationFragments.addAll(parent.requiredConfigurationFragments);
missingFragmentPolicy = parent.missingFragmentPolicy;
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.addAll(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<Boolean> 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));
Preconditions.checkState(skylarkExecutable == (configuredTargetFunction != null));
Preconditions.checkState(skylarkExecutable == (ruleDefinitionEnvironment != null));
Preconditions.checkState(workspaceOnly || externalBindingsFunction == NO_EXTERNAL_BINDINGS);
return new RuleClass(name, skylarkExecutable, documented, publicByDefault, binaryOutput,
workspaceOnly, outputsDefaultExecutable, implicitOutputsFunction, configurator,
configuredTargetFactory, validityPredicate, preferredDependencyPredicate,
ImmutableSet.copyOf(advertisedProviders), configuredTargetFunction,
externalBindingsFunction, ruleDefinitionEnvironment, configurationFragments,
ImmutableMap.copyOf(requiredFragmentNames), fragmentNameResolver, missingFragmentPolicy,
supportsConstraintChecking, attributes.values().toArray(new Attribute[0]));
}
/**
* Declares that the implementation of this rule class requires the given configuration
* fragments to be present in the configuration. The value is inherited by subclasses.
*
* <p>For backwards compatibility, if the set is empty, all fragments may be accessed. But note
* that this is only enforced in the {@link com.google.devtools.build.lib.analysis.RuleContext}
* class.
*/
public Builder requiresConfigurationFragments(Class<?>... configurationFragment) {
Collections.addAll(configurationFragments, configurationFragment);
return this;
}
/**
* Sets the policy for the case where the configuration is missing required fragments (see
* {@link #requiresConfigurationFragments}).
*/
public Builder setMissingFragmentPolicy(MissingFragmentPolicy missingFragmentPolicy) {
this.missingFragmentPolicy = missingFragmentPolicy;
return this;
}
/**
* Declares the configuration fragments that are required by this rule.
*
* <p>In contrast to {@link #requiresConfigurationFragments(Class...)}, this method a) takes the
* names of fragments instead of their classes and b) distinguishes whether the fragments can be
* accessed in host (HOST) or target (NONE) configuration.
*/
public Builder requiresConfigurationFragments(
FragmentClassNameResolver fragmentNameResolver,
Map<ConfigurationTransition, ImmutableSet<String>> configurationFragmentNames) {
requiredFragmentNames.putAll(configurationFragmentNames);
this.fragmentNameResolver = fragmentNameResolver;
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.
*
* <p>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}.
*
* <p>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);
this.configurator = configurator;
return this;
}
public Builder factory(ConfiguredTargetFactory<?, ?> factory) {
this.configuredTargetFactory = factory;
return this;
}
public Builder setValidityPredicate(PredicateWithMessage<Rule> predicate) {
this.validityPredicate = predicate;
return this;
}
public Builder setPreferredDependencyPredicate(Predicate<String> predicate) {
this.preferredDependencyPredicate = predicate;
return this;
}
/**
* State that the rule class being built possibly supplies the specified provider to its direct
* dependencies.
*
* <p>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 <b>must</b> implement
* said provider, merely that it <b>can</b>. After the configured target is constructed from
* this rule, aspects will be filtered according to the set of actual providers.
*
* <p>This is here so that we can do the loading phase overestimation required for
* "blaze query", which does not have the configured targets available.
*
* <p>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) {
Collections.addAll(advertisedProviders, providers);
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 <TYPE> Builder add(Attribute.Builder<TYPE> 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 <TYPE> Builder override(Attribute.Builder<TYPE> attr) {
overrideAttribute(attr.build());
return this;
}
/**
* Adds or overrides the attribute in the rule class. Meant for Skylark usage.
*/
public void addOrOverrideAttribute(Attribute attribute) {
if (attributes.containsKey(attribute.getName())) {
overrideAttribute(attribute);
} else {
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<? super Rule, Map<String, Label>> func) {
this.externalBindingsFunction = func;
return this;
}
/**
* Sets the rule definition environment. Meant for Skylark usage.
*/
public Builder setRuleDefinitionEnvironment(Environment env) {
this.ruleDefinitionEnvironment = env;
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 <TYPE> 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 <TYPE> 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 <TYPE> 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.
*
* <p>The input list cannot be empty.
*/
public <TYPE> Builder restrictedTo(Label firstEnvironment, Label... otherEnvironments) {
ImmutableList<Label> environments = ImmutableList.<Label>builder().add(firstEnvironment)
.add(otherEnvironments).build();
add(attr(DEFAULT_RESTRICTED_ENVIRONMENT_ATTR, LABEL_LIST).cfg(HOST).value(environments));
return this;
}
/**
* Exempts rules of this type from the constraint enforcement system. This should only be
* applied to rules that are intrinsically incompatible with constraint checking (any
* application of this method weakens the reach and strength of the system).
*
* @param reason user-informative message explaining the reason for exemption (not used)
*/
public <TYPE> Builder exemptFromConstraintChecking(String reason) {
Preconditions.checkState(this.supportsConstraintChecking);
this.supportsConstraintChecking = false;
attributes.remove(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR);
attributes.remove(RuleClass.RESTRICTED_ENVIRONMENT_ATTR);
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();
}
}
public static Builder createPlaceholderBuilder(final String name, final Location ruleLocation,
ImmutableList<RuleClass> parents) {
return new Builder(name, RuleClassType.PLACEHOLDER, /*skylark=*/true,
parents.toArray(new RuleClass[parents.size()])).factory(
new ConfiguredTargetFactory<Object, Object>() {
@Override
public Object create(Object ruleContext) throws InterruptedException {
throw new IllegalStateException(
"Cannot create configured targets from rule with placeholder class named \"" + name
+ "\" at " + ruleLocation);
}
});
}
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 skylarkExecutable;
private final boolean documented;
private final boolean publicByDefault;
private final boolean binaryOutput;
private final boolean workspaceOnly;
private final boolean outputsDefaultExecutable;
/**
* A (unordered) mapping from attribute names to small integers indexing into
* the {@code attributes} array.
*/
private final Map<String, Integer> attributeIndex = new HashMap<>();
/**
* All attributes of this rule class (including inherited ones) ordered by
* attributeIndex value.
*/
private final ImmutableList<Attribute> attributes;
/**
* 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;
/**
* 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<Rule> validityPredicate;
/**
* See {@link #isPreferredDependency}.
*/
private final Predicate<String> preferredDependencyPredicate;
/**
* The list of transitive info providers this class advertises to aspects.
*/
private final ImmutableSet<Class<?>> 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<? super Rule, Map<String, Label>> externalBindingsFunction;
/**
* The Skylark rule definition environment of this RuleClass.
* Null for non Skylark executable RuleClasses.
*/
@Nullable private final Environment ruleDefinitionEnvironment;
/**
* The set of required configuration fragments; this should list all fragments that can be
* accessed by the rule implementation. If empty, all fragments are allowed to be accessed for
* backwards compatibility.
*/
private final ImmutableSet<Class<?>> requiredConfigurationFragments;
/**
* A dictionary that maps configurations (NONE for target configuration, HOST for host
* configuration) to lists of names of required configuration fragments.
*/
private final ImmutableMap<ConfigurationTransition, ImmutableSet<String>>
requiredConfigurationFragmentNames;
/**
* Used to resolve the names of fragments in order to compare them to values in {@link
* #requiredConfigurationFragmentNames}
*/
private final FragmentClassNameResolver fragmentNameResolver;
/**
* What to do during analysis if a configuration fragment is missing.
*/
private final MissingFragmentPolicy missingFragmentPolicy;
/**
* 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;
/**
* Helper constructor that skips allowedConfigurationFragmentNames and fragmentNameResolver
*/
@VisibleForTesting
RuleClass(String name,
boolean skylarkExecutable,
boolean documented,
boolean publicByDefault,
boolean binaryOutput,
boolean workspaceOnly,
boolean outputsDefaultExecutable,
ImplicitOutputsFunction implicitOutputsFunction,
Configurator<?, ?> configurator,
ConfiguredTargetFactory<?, ?> configuredTargetFactory,
PredicateWithMessage<Rule> validityPredicate,
Predicate<String> preferredDependencyPredicate,
ImmutableSet<Class<?>> advertisedProviders,
@Nullable BaseFunction configuredTargetFunction,
Function<? super Rule, Map<String, Label>> externalBindingsFunction,
@Nullable Environment ruleDefinitionEnvironment,
Set<Class<?>> allowedConfigurationFragments,
MissingFragmentPolicy missingFragmentPolicy,
boolean supportsConstraintChecking,
Attribute... attributes) {
this(name,
skylarkExecutable,
documented,
publicByDefault,
binaryOutput,
workspaceOnly,
outputsDefaultExecutable,
implicitOutputsFunction,
configurator,
configuredTargetFactory,
validityPredicate,
preferredDependencyPredicate,
advertisedProviders,
configuredTargetFunction,
externalBindingsFunction,
ruleDefinitionEnvironment,
allowedConfigurationFragments,
ImmutableMap.<ConfigurationTransition, ImmutableSet<String>>of(),
null, // FragmentClassNameResolver
missingFragmentPolicy,
supportsConstraintChecking,
attributes);
}
/**
* 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:
* <ul>
* <li>if the parameter is a file, it is allowed if its file type is given
* in {@code depsAllowedFiles},
* <li>if the parameter is a rule and the rule class is accepted by
* {@code depsAllowedRules}, then it is allowed,
* <li>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;
* <li>all other parameters trigger an error.
* </ul>
*
* <p>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.
* @param workspaceOnly
*/
@VisibleForTesting
RuleClass(String name,
boolean skylarkExecutable, boolean documented, boolean publicByDefault,
boolean binaryOutput, boolean workspaceOnly, boolean outputsDefaultExecutable,
ImplicitOutputsFunction implicitOutputsFunction,
Configurator<?, ?> configurator,
ConfiguredTargetFactory<?, ?> configuredTargetFactory,
PredicateWithMessage<Rule> validityPredicate, Predicate<String> preferredDependencyPredicate,
ImmutableSet<Class<?>> advertisedProviders,
@Nullable BaseFunction configuredTargetFunction,
Function<? super Rule, Map<String, Label>> externalBindingsFunction,
@Nullable Environment ruleDefinitionEnvironment,
Set<Class<?>> allowedConfigurationFragments,
ImmutableMap<ConfigurationTransition, ImmutableSet<String>> allowedConfigurationFragmentNames,
@Nullable FragmentClassNameResolver fragmentNameResolver,
MissingFragmentPolicy missingFragmentPolicy,
boolean supportsConstraintChecking,
Attribute... attributes) {
this.name = name;
this.targetKind = name + " rule";
this.skylarkExecutable = skylarkExecutable;
this.documented = documented;
this.publicByDefault = publicByDefault;
this.binaryOutput = binaryOutput;
this.implicitOutputsFunction = implicitOutputsFunction;
this.configurator = Preconditions.checkNotNull(configurator);
this.configuredTargetFactory = configuredTargetFactory;
this.validityPredicate = validityPredicate;
this.preferredDependencyPredicate = preferredDependencyPredicate;
this.advertisedProviders = advertisedProviders;
this.configuredTargetFunction = configuredTargetFunction;
this.externalBindingsFunction = externalBindingsFunction;
this.ruleDefinitionEnvironment = ruleDefinitionEnvironment;
this.attributes = ImmutableList.copyOf(attributes);
this.workspaceOnly = workspaceOnly;
this.outputsDefaultExecutable = outputsDefaultExecutable;
this.requiredConfigurationFragments = ImmutableSet.copyOf(allowedConfigurationFragments);
this.requiredConfigurationFragmentNames = allowedConfigurationFragmentNames;
this.fragmentNameResolver = fragmentNameResolver;
this.missingFragmentPolicy = missingFragmentPolicy;
this.supportsConstraintChecking = supportsConstraintChecking;
// create the index:
int index = 0;
for (Attribute attribute : attributes) {
attributeIndex.put(attribute.getName(), index++);
}
}
/**
* Returns the function which determines the set of implicit outputs
* generated by a given rule.
*
* <p>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.
*
* <p>Implicit outputs are a widely-relied upon. All ".so",
* and "_deploy.jar" targets referenced in BUILD files are examples.
*/
@VisibleForTesting
public ImplicitOutputsFunction getImplicitOutputsFunction() {
return implicitOutputsFunction;
}
@SuppressWarnings("unchecked")
public <C, R> Configurator<C, R> getConfigurator() {
return (Configurator<C, R>) configurator;
}
@SuppressWarnings("unchecked")
public <CT, RC> ConfiguredTargetFactory<CT, RC> getConfiguredTargetFactory() {
return (ConfiguredTargetFactory<CT, RC>) 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.)
*
* <p>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 if not found.
*/
public Attribute getAttributeByName(String attrName) {
return attributes.get(getAttributeIndex(attrName));
}
/**
* 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<Attribute> getAttributes() {
return attributes;
}
public PredicateWithMessage<Rule> getValidityPredicate() {
return validityPredicate;
}
/**
* Returns the set of advertised transitive info providers.
*
* <p>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 <b>must</b> implement
* said provider, merely that it <b>can</b>. After the configured target is constructed from this
* rule, aspects will be filtered according to the set of actual providers.
*
* <p>This is here so that we can do the loading phase overestimation required for "blaze query",
* which does not have the configured targets available.
*
* <p>This should in theory only contain subclasses of
* {@link com.google.devtools.build.lib.analysis.TransitiveInfoProvider}, but
* our current dependency structure does not allow a reference to that class here.
*/
public ImmutableSet<Class<?>> 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);
}
/**
* The set of required configuration fragments; this contains all fragments that can be
* accessed by the rule implementation. If empty, all fragments are allowed to be accessed for
* backwards compatibility.
*/
public Set<Class<?>> getRequiredConfigurationFragments() {
return requiredConfigurationFragments;
}
/**
* Checks if the configuration fragment may be accessed (i.e., if it's declared) in the specified
* configuration (target or host).
*/
public boolean isLegalConfigurationFragment(
Class<?> configurationFragment, ConfigurationTransition config) {
return requiredConfigurationFragments.contains(configurationFragment)
|| hasLegalFragmentName(configurationFragment, config);
}
public boolean isLegalConfigurationFragment(Class<?> configurationFragment) {
// NONE means target configuration.
return isLegalConfigurationFragment(configurationFragment, ConfigurationTransition.NONE);
}
/**
* Checks whether the name of the given fragment class was declared as required fragment in the
* specified configuration (target or host).
*/
private boolean hasLegalFragmentName(
Class<?> configurationFragment, ConfigurationTransition config) {
if (fragmentNameResolver == null) {
return false;
}
String name = fragmentNameResolver.resolveName(configurationFragment);
return (name != null && requiredConfigurationFragmentNames.get(config).contains(name));
}
/**
* Whether to fail analysis if any of the required configuration fragments are missing.
*/
public MissingFragmentPolicy missingFragmentPolicy() {
return missingFragmentPolicy;
}
/**
* Returns true if rules of this type can be used with the constraint enforcement system.
*/
public boolean supportsConstraintChecking() {
return supportsConstraintChecking;
}
/**
* Helper function for {@link RuleFactory#createAndAddRule}.
*/
Rule createRuleWithLabel(Package.Builder pkgBuilder, Label ruleLabel,
Map<String, Object> attributeValues, EventHandler eventHandler, FuncallExpression ast,
Location location) throws SyntaxException {
Rule rule = pkgBuilder.newRuleWithLabel(ruleLabel, this, null, location);
createRuleCommon(rule, pkgBuilder, attributeValues, eventHandler, ast);
return rule;
}
private void createRuleCommon(Rule rule, Package.Builder pkgBuilder,
Map<String, Object> attributeValues, EventHandler eventHandler, FuncallExpression ast)
throws SyntaxException {
populateRuleAttributeValues(
rule, pkgBuilder, attributeValues, eventHandler, ast);
rule.populateOutputFiles(eventHandler, pkgBuilder);
rule.checkForNullLabels();
rule.checkValidityPredicate(eventHandler);
}
static class ParsedAttributeValue {
private final boolean explicitlySpecified;
private final Object value;
private final Location location;
ParsedAttributeValue(boolean explicitlySpecified, Object value, Location location) {
this.explicitlySpecified = explicitlySpecified;
this.value = value;
this.location = location;
}
public boolean getExplicitlySpecified() {
return explicitlySpecified;
}
public Object getValue() {
return value;
}
public Location getLocation() {
return location;
}
}
/**
* Creates a rule with the attribute values that are already parsed.
*
* <p><b>WARNING:</b> This assumes that the attribute values here have the right type and
* bypasses some sanity checks. If they are of the wrong type, everything will come down burning.
*/
@SuppressWarnings("unchecked")
Rule createRuleWithParsedAttributeValues(Label label,
Package.Builder pkgBuilder, Location ruleLocation,
Map<String, ParsedAttributeValue> attributeValues, EventHandler eventHandler)
throws SyntaxException{
Rule rule = pkgBuilder.newRuleWithLabel(label, this, null, ruleLocation);
rule.checkValidityPredicate(eventHandler);
for (Attribute attribute : rule.getRuleClassObject().getAttributes()) {
ParsedAttributeValue value = attributeValues.get(attribute.getName());
if (attribute.isMandatory()) {
Preconditions.checkState(value != null);
}
if (value == null) {
continue;
}
rule.setAttributeValue(attribute, value.getValue(), value.getExplicitlySpecified());
rule.setAttributeLocation(attribute, value.getLocation());
checkAllowedValues(rule, attribute, eventHandler);
if (attribute.getName().equals("visibility")) {
// TODO(bazel-team): Verify that this cast works
rule.setVisibility(PackageFactory.getVisibility((List<Label>) value.getValue()));
}
}
rule.populateOutputFiles(eventHandler, pkgBuilder);
Preconditions.checkState(!rule.containsErrors());
return rule;
}
/**
* Populates the attributes table of new rule "rule" from the
* "attributeValues" mapping from attribute names to values in the build
* language. Errors are reported on "reporter". "ast" is used to associate
* location information with each rule attribute.
*/
private void populateRuleAttributeValues(Rule rule,
Package.Builder pkgBuilder,
Map<String, Object> attributeValues,
EventHandler eventHandler,
FuncallExpression ast) {
BitSet definedAttrs = new BitSet(); // set of attr indices
for (Map.Entry<String, Object> entry : attributeValues.entrySet()) {
String attributeName = entry.getKey();
Object attributeValue = entry.getValue();
if (attributeValue == Runtime.NONE) { // Ignore all None values.
continue;
}
Integer attrIndex = setRuleAttributeValue(rule, eventHandler, attributeName, attributeValue);
if (attrIndex != null) {
definedAttrs.set(attrIndex);
checkAttrValNonEmpty(rule, eventHandler, attributeValue, attrIndex);
}
}
// Save the location of each non-default attribute definition:
if (ast != null) {
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());
}
}
}
}
List<Attribute> attrsWithComputedDefaults = new ArrayList<>();
// Set defaults; ensure that every mandatory attribute has a value. Use
// the default if none is specified.
int numAttributes = getAttributeCount();
for (int attrIndex = 0; attrIndex < numAttributes; ++attrIndex) {
if (!definedAttrs.get(attrIndex)) {
Attribute attr = getAttribute(attrIndex);
if (attr.isMandatory()) {
rule.reportError(rule.getLabel() + ": missing value for mandatory "
+ "attribute '" + attr.getName() + "' in '"
+ name + "' rule", eventHandler);
}
if (attr.hasComputedDefault()) {
attrsWithComputedDefaults.add(attr);
} else {
Object defaultValue = getAttributeNoncomputedDefaultValue(attr, pkgBuilder);
checkAttrValNonEmpty(rule, eventHandler, defaultValue, attrIndex);
rule.setAttributeValue(attr, defaultValue, /*explicit=*/false);
checkAllowedValues(rule, attr, eventHandler);
}
}
}
// Evaluate and set any computed defaults now that all non-computed
// TODO(bazel-team): remove this special casing. Thanks to configurable attributes refactoring,
// computed defaults don't get bound to their final values at this point, so we no longer
// have to wait until regular attributes have been initialized.
for (Attribute attr : attrsWithComputedDefaults) {
rule.setAttributeValue(attr, attr.getDefaultValue(rule), /*explicit=*/false);
}
// Now that all attributes are bound to values, collect and store configurable attribute keys.
populateConfigDependenciesAttribute(rule);
checkForDuplicateLabels(rule, eventHandler);
checkThirdPartyRuleHasLicense(rule, pkgBuilder, eventHandler);
checkForValidSizeAndTimeoutValues(rule, eventHandler);
}
/**
* 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<Label> configLabels = new LinkedHashSet<>();
for (Attribute attr : rule.getAttributes()) {
Type.SelectorList<?> selectors = attributes.getSelectorList(attr.getName(), attr.getType());
if (selectors != null) {
configLabels.addAll(selectors.getKeyLabels());
}
}
rule.setAttributeValue(configDepsAttribute, ImmutableList.copyOf(configLabels),
/*explicit=*/false);
}
private void checkAttrValNonEmpty(
Rule rule, EventHandler eventHandler, Object attributeValue, Integer attrIndex) {
if (attributeValue instanceof List<?>) {
Attribute attr = getAttribute(attrIndex);
if (attr.isNonEmpty() && ((List<?>) attributeValue).isEmpty()) {
rule.reportError(rule.getLabel() + ": non empty " + "attribute '" + attr.getName()
+ "' in '" + name + "' rule '" + rule.getLabel() + "' has to have at least one value",
eventHandler);
}
}
}
/**
* Report an error for each label that appears more than once in a LABEL_LIST attribute
* of the given rule.
*
* @param rule The rule.
* @param eventHandler The eventHandler to use to report the duplicated deps.
*/
private static void checkForDuplicateLabels(Rule rule, EventHandler eventHandler) {
for (Attribute attribute : rule.getAttributes()) {
if (attribute.getType() == Type.LABEL_LIST) {
checkForDuplicateLabels(rule, attribute, eventHandler);
}
}
}
/**
* Reports an error against the specified rule if it's beneath third_party
* but does not have a declared license.
*/
private static void checkThirdPartyRuleHasLicense(Rule rule,
Package.Builder pkgBuilder, EventHandler eventHandler) {
if (rule.getLabel().getPackageName().startsWith("third_party/")) {
License license = rule.getLicense();
if (license == null) {
license = pkgBuilder.getDefaultLicense();
}
if (license == License.NO_LICENSE) {
rule.reportError("third-party rule '" + rule.getLabel() + "' lacks a license declaration "
+ "with one of the following types: notice, reciprocal, permissive, "
+ "restricted, unencumbered, by_exception_only",
eventHandler);
}
}
}
/**
* Report an error for each label that appears more than once in the given attribute
* of the given rule.
*
* @param rule The rule.
* @param attribute The attribute to check. Must exist in rule and be of type LABEL_LIST.
* @param eventHandler The eventHandler to use to report the duplicated deps.
*/
private static void checkForDuplicateLabels(Rule rule, Attribute attribute,
EventHandler eventHandler) {
Set<Label> duplicates = AggregatingAttributeMapper.of(rule).checkForDuplicateLabels(attribute);
for (Label label : duplicates) {
rule.reportError(
String.format("Label '%s' is duplicated in the '%s' attribute of rule '%s'",
label, attribute.getName(), rule.getName()), eventHandler);
}
}
/**
* Report an error if the rule has a timeout or size attribute that is not a
* legal value. These attributes appear on all tests.
*
* @param rule the rule to check
* @param eventHandler the eventHandler to use to report the duplicated deps
*/
private static void checkForValidSizeAndTimeoutValues(Rule rule, EventHandler eventHandler) {
if (rule.getRuleClassObject().hasAttr("size", Type.STRING)) {
String size = NonconfigurableAttributeMapper.of(rule).get("size", Type.STRING);
if (TestSize.getTestSize(size) == null) {
rule.reportError(
String.format("In rule '%s', size '%s' is not a valid size.", rule.getName(), size),
eventHandler);
}
}
if (rule.getRuleClassObject().hasAttr("timeout", Type.STRING)) {
String timeout = NonconfigurableAttributeMapper.of(rule).get("timeout", Type.STRING);
if (TestTimeout.getTestTimeout(timeout) == null) {
rule.reportError(
String.format(
"In rule '%s', timeout '%s' is not a valid timeout.", rule.getName(), timeout),
eventHandler);
}
}
}
/**
* Returns the default value for the specified rule attribute.
*
* <p>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.
*
* <p>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 "attrName" in rule "rule", by converting the
* build-language value "attrVal" to the appropriate type for the attribute.
* Returns the attribute index iff successful, null otherwise.
*
* <p>In case of failure, error messages are reported on "handler", and "rule"
* is marked as containing errors.
*/
@SuppressWarnings("unchecked")
private Integer setRuleAttributeValue(Rule rule,
EventHandler eventHandler,
String attrName,
Object attrVal) {
if (attrName.equals("name")) {
return null; // "name" is handled specially
}
Integer attrIndex = getAttributeIndex(attrName);
if (attrIndex == null) {
rule.reportError(rule.getLabel() + ": no such attribute '" + attrName
+ "' in '" + name + "' rule", eventHandler);
return null;
}
Attribute attr = getAttribute(attrIndex);
Object converted;
try {
String what = "attribute '" + attrName + "' in '" + name + "' rule";
converted = attr.getType().selectableConvert(attrVal, what, rule.getLabel());
if ((converted instanceof Type.SelectorList<?>) && !attr.isConfigurable()) {
rule.reportError(rule.getLabel() + ": attribute \"" + attr.getName()
+ "\" is not configurable", eventHandler);
return null;
}
if ((converted instanceof List<?>) && !(converted instanceof GlobList<?>)) {
if (attr.isOrderIndependent()) {
converted = Ordering.natural().sortedCopy((List<? extends Comparable<?>>) converted);
}
converted = ImmutableList.copyOf((List<?>) converted);
}
} catch (Type.ConversionException e) {
rule.reportError(rule.getLabel() + ": " + e.getMessage(), eventHandler);
return null;
}
if (attrName.equals("visibility")) {
List<Label> attrList = (List<Label>) converted;
if (!attrList.isEmpty()
&& ConstantRuleVisibility.LEGACY_PUBLIC_LABEL.equals(attrList.get(0))) {
rule.reportError(rule.getLabel() + ": //visibility:legacy_public only allowed in package "
+ "declaration", eventHandler);
}
rule.setVisibility(PackageFactory.getVisibility(attrList));
}
rule.setAttributeValue(attr, converted, /*explicit=*/true);
checkAllowedValues(rule, attr, eventHandler);
return attrIndex;
}
/**
* Verifies that the rule has a valid value for the attribute according to its allowed values.
*
* <p>If the value for the given attribute on the given rule is invalid, an error will be recorded
* in the given EventHandler.
*
* <p>If the rule is configurable, all of its potential values are evaluated, and errors for each
* of the invalid values are reported.
*/
private void checkAllowedValues(Rule rule, Attribute attribute, EventHandler eventHandler) {
if (attribute.checkAllowedValues()) {
PredicateWithMessage<Object> 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(rule.getLabel() + ": invalid value in '%s' attribute: %s",
attribute.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
* <i>bin</i> directory, false if beneath <i>genfiles</i>. 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<? super Rule, Map<String, Label>> getExternalBindingsFunction() {
return externalBindingsFunction;
}
/**
* Returns this RuleClass's rule definition environment.
*/
@Nullable public Environment getRuleDefinitionEnvironment() {
return ruleDefinitionEnvironment;
}
/**
* 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 rule class outputs a default executable for every rule.
*/
public boolean outputsDefaultExecutable() {
return outputsDefaultExecutable;
}
}