com.google.devtools.build.lib.packages.Rule.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.packages.Rule.java

Source

// 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 com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.License.DistributionType;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.GlobList;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.BinaryPredicate;
import com.google.devtools.build.lib.util.Preconditions;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * An instance of a build rule in the build language.  A rule has a name, a
 * package to which it belongs, a class such as <code>cc_library</code>, and
 * set of typed attributes.  The set of attribute names and types is a property
 * of the rule's class.  The use of the term "class" here has nothing to do
 * with Java classes.  All rules are implemented by the same Java classes, Rule
 * and RuleClass.
 *
 * <p>Here is a typical rule as it appears in a BUILD file:
 * <pre>
 * cc_library(name = 'foo',
 *            defines = ['-Dkey=value'],
 *            srcs = ['foo.cc'],
 *            deps = ['bar'])
 * </pre>
 */
public final class Rule implements Target, DependencyFilter.AttributeInfoProvider {

    /** Label predicate that allows every label. */
    public static final Predicate<Label> ALL_LABELS = Predicates.alwaysTrue();

    private final Label label;

    private final Package pkg;

    private final RuleClass ruleClass;

    private final AttributeContainer attributes;

    private RuleVisibility visibility;

    private boolean containsErrors;

    private final Location location;

    private final ImplicitOutputsFunction implicitOutputsFunction;

    // Initialized in the call to populateOutputFiles.
    private List<OutputFile> outputFiles;
    private ListMultimap<String, OutputFile> outputFileMap;

    Rule(Package pkg, Label label, RuleClass ruleClass, Location location, AttributeContainer attributeContainer) {
        this(pkg, label, ruleClass, location, attributeContainer, ruleClass.getDefaultImplicitOutputsFunction());
    }

    Rule(Package pkg, Label label, RuleClass ruleClass, Location location, AttributeContainer attributeContainer,
            ImplicitOutputsFunction implicitOutputsFunction) {
        this.pkg = Preconditions.checkNotNull(pkg);
        this.label = label;
        this.ruleClass = Preconditions.checkNotNull(ruleClass);
        this.location = Preconditions.checkNotNull(location);
        this.attributes = attributeContainer;
        this.implicitOutputsFunction = implicitOutputsFunction;
        this.containsErrors = false;
    }

    void setVisibility(RuleVisibility visibility) {
        this.visibility = visibility;
    }

    void setAttributeValue(Attribute attribute, Object value, boolean explicit) {
        attributes.setAttributeValue(attribute, value, explicit);
    }

    void setAttributeValueByName(String attrName, Object value) {
        attributes.setAttributeValueByName(attrName, value);
    }

    void setAttributeLocation(int attrIndex, Location location) {
        attributes.setAttributeLocation(attrIndex, location);
    }

    void setContainsErrors() {
        this.containsErrors = true;
    }

    @Override
    public Label getLabel() {
        return label;
    }

    @Override
    public String getName() {
        return label.getName();
    }

    @Override
    public Package getPackage() {
        return pkg;
    }

    public RuleClass getRuleClassObject() {
        return ruleClass;
    }

    @Override
    public String getTargetKind() {
        return ruleClass.getTargetKind();
    }

    /**
     * Returns the class of this rule. (e.g. "cc_library")
     */
    public String getRuleClass() {
        return ruleClass.getName();
    }

    /**
     * Returns the build features that apply to this rule.
     */
    public ImmutableSet<String> getFeatures() {
        return pkg.getFeatures();
    }

    /**
     * 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.
     */
    public boolean hasBinaryOutput() {
        return ruleClass.getName().equals("genrule") // this is unfortunate...
                ? NonconfigurableAttributeMapper.of(this).get("output_to_bindir", Type.BOOLEAN)
                : ruleClass.hasBinaryOutput();
    }

    /**
     * Returns true iff there were errors while constructing this rule, such as
     * attributes with missing values or values of the wrong type.
     */
    public boolean containsErrors() {
        return containsErrors;
    }

    /**
     * Returns an (unmodifiable, unordered) collection containing all the
     * Attribute definitions for this kind of rule.  (Note, this doesn't include
     * the <i>values</i> of the attributes, merely the schema.  Call
     * get[Type]Attr() methods to access the actual values.)
     */
    public Collection<Attribute> getAttributes() {
        return ruleClass.getAttributes();
    }

    /**
     * Returns true if this rule has any attributes that are configurable.
     *
     * <p>Note this is *not* the same as having attribute *types* that are configurable. For example,
     * "deps" is configurable, in that one can write a rule that sets "deps" to a configuration
     * dictionary. But if *this* rule's instance of "deps" doesn't do that, its instance
     * of "deps" is not considered configurable.
     *
     * <p>In other words, this method signals which rules might have their attribute values
     * influenced by the configuration.
     */
    public boolean hasConfigurableAttributes() {
        for (Attribute attribute : getAttributes()) {
            if (AbstractAttributeMapper.isConfigurable(this, attribute.getName(), attribute.getType())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the given attribute is configurable.
     */
    public boolean isConfigurableAttribute(String attributeName) {
        Attribute attribute = ruleClass.getAttributeByNameMaybe(attributeName);
        return attribute != null ? AbstractAttributeMapper.isConfigurable(this, attributeName, attribute.getType())
                : false;
    }

    /**
     * Returns the attribute definition whose name is {@code attrName}, or null
     * if not found.  (Use get[X]Attr for the actual value.)
     *
     * @deprecated use {@link AbstractAttributeMapper#getAttributeDefinition} instead
     */
    @Deprecated
    public Attribute getAttributeDefinition(String attrName) {
        return ruleClass.getAttributeByNameMaybe(attrName);
    }

    /**
     * Returns an (unmodifiable, ordered) collection containing all the declared output files of this
     * rule.
     *
     * <p>All implicit output files (declared in the {@link RuleClass}) are
     * listed first, followed by any explicit files (declared via the 'outs' attribute). Additionally
     * both implicit and explicit outputs will retain the relative order in which they were declared.
     *
     * <p>This ordering is useful because it is propagated through to the list of targets returned by
     * getOuts() and allows targets to access their implicit outputs easily via
     * {@code getOuts().get(N)} (providing that N is less than the number of implicit outputs).
     *
     * <p>The fact that the relative order of the explicit outputs is also retained is less obviously
     * useful but is still well defined.
     */
    public Collection<OutputFile> getOutputFiles() {
        return outputFiles;
    }

    /**
     * Returns an (unmodifiable, ordered) map containing the list of output files for every
     * output type attribute.
     */
    public ListMultimap<String, OutputFile> getOutputFileMap() {
        return outputFileMap;
    }

    @Override
    public Location getLocation() {
        return location;
    }

    public ImplicitOutputsFunction getImplicitOutputsFunction() {
        return implicitOutputsFunction;
    }

    @Override
    public Rule getAssociatedRule() {
        return this;
    }

    /**
     * Returns this rule's raw attribute info, suitable for being fed into an
     * {@link AttributeMap} for user-level attribute access. Don't use this method
     * for direct attribute access.
     */
    public AttributeContainer getAttributeContainer() {
        return attributes;
    }

    /********************************************************************
     * Attribute accessor functions.
     *
     * The below provide access to attribute definitions and other generic
     * metadata.
     *
     * For access to attribute *values* (e.g. "What's the value of attribute
     * X for Rule Y?"), go through {@link RuleContext#attributes}. If no
     * RuleContext is available, create a localized {@link AbstractAttributeMapper}
     * instance instead.
     ********************************************************************/

    /**
     * Returns the default value for the attribute {@code attrName}, which may be
     * of any type, but must exist (an exception is thrown otherwise).
     */
    public Object getAttrDefaultValue(String attrName) {
        Object defaultValue = ruleClass.getAttributeByName(attrName).getDefaultValue(this);
        // Computed defaults not expected here.
        Preconditions.checkState(!(defaultValue instanceof Attribute.ComputedDefault));
        return defaultValue;
    }

    /**
     * Returns true iff the rule class has an attribute with the given name and type.
     *
     * <p>Note: RuleContext also has isAttrDefined(), which takes Aspects into account. Whenever
     * possible, use RuleContext.isAttrDefined() instead of this method.
     */
    public boolean isAttrDefined(String attrName, Type<?> type) {
        return ruleClass.hasAttr(attrName, type);
    }

    @Override
    public boolean isAttributeValueExplicitlySpecified(Attribute attribute) {
        return attributes.isAttributeValueExplicitlySpecified(attribute);
    }

    /**
     * Returns true iff the value of the specified attribute is explicitly set in the BUILD file (as
     * opposed to its default value). This also returns true if the value from the BUILD file is the
     * same as the default value. In addition, this method return false if the rule has no attribute
     * with the given name.
     */
    public boolean isAttributeValueExplicitlySpecified(String attrName) {
        return attributes.isAttributeValueExplicitlySpecified(attrName);
    }

    /**
     * Returns the location of the attribute definition for this rule, if known;
     * or the location of the whole rule otherwise. "attrName" need not be a
     * valid attribute name for this rule.
     *
     * <p>This method ignores whether the present rule was created by a macro or not.
     */
    public Location getAttributeLocationWithoutMacro(String attrName) {
        return getAttributeLocation(attrName, false /* useBuildLocation */);
    }

    /**
     * Returns the location of the attribute definition for this rule, if known;
     * or the location of the whole rule otherwise. "attrName" need not be a
     * valid attribute name for this rule.
     *
     * <p>If this rule was created by a macro, this method returns the
     * location of the macro invocation in the BUILD file instead.
     */
    public Location getAttributeLocation(String attrName) {
        return getAttributeLocation(attrName, true /* useBuildLocation */);
    }

    private Location getAttributeLocation(String attrName, boolean useBuildLocation) {
        /*
         * If the rule was created by a macro, we have to deal with two locations: one in the BUILD
         * file where the macro is invoked and one in the bzl file where the rule is created.
         * For error reporting, we are usually more interested in the former one.
         * Different methods in this class refer to different locations, though:
         * - getLocation() points to the location of the macro invocation in the BUILD file (thanks to
         *   RuleFactory).
         * - attributes.getAttributeLocation() points to the location in the bzl file.
         */
        if (wasCreatedByMacro() && useBuildLocation) {
            return getLocation();
        }

        Location attrLocation = null;
        if (!attrName.equals("name")) {
            attrLocation = attributes.getAttributeLocation(attrName);
        }
        return attrLocation != null ? attrLocation : getLocation();
    }

    /**
     * Returns whether this rule was created by a macro.
     */
    public boolean wasCreatedByMacro() {
        return hasStringAttribute("generator_name") || hasStringAttribute("generator_function");
    }

    private boolean hasStringAttribute(String attrName) {
        Object value = attributes.getAttr(attrName);
        if (value != null && value instanceof String) {
            return !((String) value).isEmpty();
        }
        return false;
    }

    /** Returns a new List instance containing all direct dependencies (all types). */
    public Collection<Label> getLabels() throws InterruptedException {
        final List<Label> labels = Lists.newArrayList();
        AggregatingAttributeMapper.of(this).visitLabels(new AttributeMap.AcceptsLabelAttribute() {
            @Override
            public void acceptLabelAttribute(Label label, Attribute attribute) {
                labels.add(label);
            }
        });
        return labels;
    }

    /**
     * Returns a new Collection containing all Labels that match a given Predicate, not including
     * outputs.
     *
     * @param predicate A binary predicate that determines if a label should be included in the
     *     result. The predicate is evaluated with this rule and the attribute that contains the
     *     label. The label will be contained in the result iff (the predicate returned {@code true}
     *     and the labels are not outputs)
     */
    public Collection<Label> getLabels(BinaryPredicate<? super Rule, Attribute> predicate)
            throws InterruptedException {
        return ImmutableSortedSet.copyOf(getTransitions(predicate).values());
    }

    /**
     * Returns a new Multimap containing all attributes that match a given Predicate and corresponding
     * labels, not including outputs.
     *
     * @param predicate A binary predicate that determines if a label should be included in the
     *     result. The predicate is evaluated with this rule and the attribute that contains the
     *     label. The label will be contained in the result iff (the predicate returned {@code true}
     *     and the labels are not outputs)
     */
    public Multimap<Attribute, Label> getTransitions(final BinaryPredicate<? super Rule, Attribute> predicate)
            throws InterruptedException {
        final Multimap<Attribute, Label> transitions = HashMultimap.create();
        // TODO(bazel-team): move this to AttributeMap, too. Just like visitLabels, which labels should
        // be visited may depend on the calling context. We shouldn't implicitly decide this for
        // the caller.
        AggregatingAttributeMapper.of(this).visitLabels(new AttributeMap.AcceptsLabelAttribute() {
            @Override
            public void acceptLabelAttribute(Label label, Attribute attribute) {
                if (predicate.apply(Rule.this, attribute)) {
                    transitions.put(attribute, label);
                }
            }
        });
        return transitions;
    }

    /**
     * Check if this rule is valid according to the validityPredicate of its RuleClass.
     */
    void checkValidityPredicate(EventHandler eventHandler) {
        PredicateWithMessage<Rule> predicate = getRuleClassObject().getValidityPredicate();
        if (!predicate.apply(this)) {
            reportError(predicate.getErrorReason(this), eventHandler);
        }
    }

    /**
     * Collects the output files (both implicit and explicit). All the implicit output files are added
     * first, followed by any explicit files. Additionally both implicit and explicit output files
     * will retain the relative order in which they were declared.
     */
    void populateOutputFiles(EventHandler eventHandler, Package.Builder pkgBuilder)
            throws LabelSyntaxException, InterruptedException {
        Preconditions.checkState(outputFiles == null);
        // Order is important here: implicit before explicit
        outputFiles = Lists.newArrayList();
        outputFileMap = LinkedListMultimap.create();
        populateImplicitOutputFiles(eventHandler, pkgBuilder);
        populateExplicitOutputFiles(eventHandler);
        outputFiles = ImmutableList.copyOf(outputFiles);
        outputFileMap = ImmutableListMultimap.copyOf(outputFileMap);
    }

    // Explicit output files are user-specified attributes of type OUTPUT.
    private void populateExplicitOutputFiles(EventHandler eventHandler) throws LabelSyntaxException {
        NonconfigurableAttributeMapper nonConfigurableAttributes = NonconfigurableAttributeMapper.of(this);
        for (Attribute attribute : ruleClass.getAttributes()) {
            String name = attribute.getName();
            Type<?> type = attribute.getType();
            if (type == BuildType.OUTPUT) {
                Label outputLabel = nonConfigurableAttributes.get(name, BuildType.OUTPUT);
                if (outputLabel != null) {
                    addLabelOutput(attribute, outputLabel, eventHandler);
                }
            } else if (type == BuildType.OUTPUT_LIST) {
                for (Label label : nonConfigurableAttributes.get(name, BuildType.OUTPUT_LIST)) {
                    addLabelOutput(attribute, label, eventHandler);
                }
            }
        }
    }

    /**
     * Implicit output files come from rule-specific patterns, and are a function
     * of the rule's "name", "srcs", and other attributes.
     */
    private void populateImplicitOutputFiles(EventHandler eventHandler, Package.Builder pkgBuilder)
            throws InterruptedException {
        try {
            RawAttributeMapper attributeMap = RawAttributeMapper.of(this);
            for (String out : implicitOutputsFunction.getImplicitOutputs(attributeMap)) {
                try {
                    addOutputFile(pkgBuilder.createLabel(out), eventHandler);
                } catch (LabelSyntaxException e) {
                    reportError("illegal output file name '" + out + "' in rule " + getLabel() + " due to: "
                            + e.getMessage(), eventHandler);
                }
            }
        } catch (EvalException e) {
            reportError(e.print(), eventHandler);
        }
    }

    private void addLabelOutput(Attribute attribute, Label label, EventHandler eventHandler)
            throws LabelSyntaxException {
        if (!label.getPackageIdentifier().equals(pkg.getPackageIdentifier())) {
            throw new IllegalStateException("Label for attribute " + attribute + " should refer to '"
                    + pkg.getName() + "' but instead refers to '" + label.getPackageFragment() + "' (label '"
                    + label.getName() + "')");
        }
        if (label.getName().equals(".")) {
            throw new LabelSyntaxException("output file name can't be equal '.'");
        }
        OutputFile outputFile = addOutputFile(label, eventHandler);
        outputFileMap.put(attribute.getName(), outputFile);
    }

    private OutputFile addOutputFile(Label label, EventHandler eventHandler) {
        if (label.getName().equals(getName())) {
            // TODO(bazel-team): for now (23 Apr 2008) this is just a warning.  After
            // June 1st we should make it an error.
            reportWarning("target '" + getName() + "' is both a rule and a file; please choose "
                    + "another name for the rule", eventHandler);
        }
        OutputFile outputFile = new OutputFile(pkg, label, this);
        outputFiles.add(outputFile);
        return outputFile;
    }

    void reportError(String message, EventHandler eventHandler) {
        eventHandler.handle(Event.error(location, message));
        this.containsErrors = true;
    }

    void reportWarning(String message, EventHandler eventHandler) {
        eventHandler.handle(Event.warn(location, message));
    }

    /**
     * Returns a string of the form "cc_binary rule //foo:foo"
     *
     * @return a string of the form "cc_binary rule //foo:foo"
     */
    @Override
    public String toString() {
        return getRuleClass() + " rule " + getLabel();
    }

    /**
      * Returns the effective visibility of this Rule. Visibility is computed from
      * these sources in this order of preference:
      *   - 'visibility' attribute
      *   - 'default_visibility;' attribute of package() declaration
      *   - public.
      */
    @Override
    public RuleVisibility getVisibility() {
        if (visibility != null) {
            return visibility;
        }

        if (getRuleClassObject().isPublicByDefault()) {
            return ConstantRuleVisibility.PUBLIC;
        }

        return pkg.getDefaultVisibility();
    }

    public boolean isVisibilitySpecified() {
        return visibility != null;
    }

    @Override
    public boolean isConfigurable() {
        return true;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Set<DistributionType> getDistributions() {
        if (isAttrDefined("distribs", BuildType.DISTRIBUTIONS) && isAttributeValueExplicitlySpecified("distribs")) {
            return NonconfigurableAttributeMapper.of(this).get("distribs", BuildType.DISTRIBUTIONS);
        } else {
            return getPackage().getDefaultDistribs();
        }
    }

    @Override
    public License getLicense() {
        if (isAttrDefined("licenses", BuildType.LICENSE) && isAttributeValueExplicitlySpecified("licenses")) {
            return NonconfigurableAttributeMapper.of(this).get("licenses", BuildType.LICENSE);
        } else {
            return getPackage().getDefaultLicense();
        }
    }

    /**
     * Returns the license of the output of the binary created by this rule, or
     * null if it is not specified.
     */
    public License getToolOutputLicense(AttributeMap attributes) {
        if (isAttrDefined("output_licenses", BuildType.LICENSE)
                && attributes.isAttributeValueExplicitlySpecified("output_licenses")) {
            return attributes.get("output_licenses", BuildType.LICENSE);
        } else {
            return null;
        }
    }

    /**
     * Returns the globs that were expanded to create an attribute value, or
     * null if unknown or not applicable.
     */
    public static GlobList<?> getGlobInfo(Object attributeValue) {
        if (attributeValue instanceof GlobList<?>) {
            return (GlobList<?>) attributeValue;
        } else {
            return null;
        }
    }

    private void checkForNullLabel(Label labelToCheck, Object context) {
        if (labelToCheck == null) {
            throw new IllegalStateException(
                    String.format("null label in rule %s, %s", getLabel().toString(), context));
        }
    }

    // Consistency check: check if this label contains any weird labels (i.e.
    // null-valued, with a packageFragment that is null...). The bug that prompted
    // the introduction of this code is #2210848 (NullPointerException in
    // Package.checkForConflicts() ).
    void checkForNullLabels() throws InterruptedException {
        AggregatingAttributeMapper.of(this).visitLabels(new AttributeMap.AcceptsLabelAttribute() {
            @Override
            public void acceptLabelAttribute(Label labelToCheck, Attribute attribute) {
                checkForNullLabel(labelToCheck, attribute);
            }
        });
        for (OutputFile outputFile : getOutputFiles()) {
            checkForNullLabel(outputFile.getLabel(), "output file");
        }
    }

    /**
     * Returns the Set of all tags exhibited by this target.  May be empty.
     */
    public Set<String> getRuleTags() {
        Set<String> ruleTags = new LinkedHashSet<>();
        for (Attribute attribute : getRuleClassObject().getAttributes()) {
            if (attribute.isTaggable()) {
                Type<?> attrType = attribute.getType();
                String name = attribute.getName();
                // This enforces the expectation that taggable attributes are non-configurable.
                Object value = NonconfigurableAttributeMapper.of(this).get(name, attrType);
                Set<String> tags = attrType.toTagSet(value, name);
                ruleTags.addAll(tags);
            }
        }
        return ruleTags;
    }

    /**
     * Computes labels of additional dependencies that can be provided by aspects that this rule
     * can require from its direct dependencies.
     */
    public Collection<? extends Label> getAspectLabelsSuperset(DependencyFilter predicate) {
        SetMultimap<Attribute, Label> labels = LinkedHashMultimap.create();
        for (Attribute attribute : this.getAttributes()) {
            for (Aspect candidateClass : attribute.getAspects(this)) {
                AspectDefinition.addAllAttributesOfAspect(Rule.this, labels, candidateClass, predicate);
            }
        }
        return labels.values();
    }

    /**
     * @return The repository name.
     */
    public RepositoryName getRepository() {
        return getLabel().getPackageIdentifier().getRepository();
    }
}