// 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.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); } /** * {@see #isAttributeValueExplicitlySpecified(String)} */ @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. * This returns true also if the value explicity specified in the BUILD file is the same as the * attribute's 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 ImmutableList.Builder<OutputFile> outputFilesBuilder = ImmutableList.builder(); ImmutableListMultimap.Builder<String, OutputFile> outputFileMapBuilder = ImmutableListMultimap.builder(); populateImplicitOutputFiles(eventHandler, pkgBuilder, outputFilesBuilder); populateExplicitOutputFiles(eventHandler, outputFilesBuilder, outputFileMapBuilder); outputFiles = outputFilesBuilder.build(); outputFileMap = outputFileMapBuilder.build(); } // Explicit output files are user-specified attributes of type OUTPUT. private void populateExplicitOutputFiles( EventHandler eventHandler, ImmutableList.Builder<OutputFile> outputFilesBuilder, ImmutableListMultimap.Builder<String, OutputFile> outputFileMapBuilder) 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, outputFilesBuilder, outputFileMapBuilder); } } else if (type == BuildType.OUTPUT_LIST) { for (Label label : nonConfigurableAttributes.get(name, BuildType.OUTPUT_LIST)) { addLabelOutput(attribute, label, eventHandler, outputFilesBuilder, outputFileMapBuilder); } } } } /** * 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, ImmutableList.Builder<OutputFile> outputFilesBuilder) throws InterruptedException { try { RawAttributeMapper attributeMap = RawAttributeMapper.of(this); for (String out : implicitOutputsFunction.getImplicitOutputs(attributeMap)) { try { addOutputFile(pkgBuilder.createLabel(out), eventHandler, outputFilesBuilder); } catch (LabelSyntaxException e) { reportError( "illegal output file name '" + out + "' in rule " + getLabel() + " due to: " + e.getMessage(), eventHandler); } } } catch (EvalException e) { reportError(String.format("In rule %s: %s", getLabel(), e.print()), eventHandler); } } private void addLabelOutput( Attribute attribute, Label label, EventHandler eventHandler, ImmutableList.Builder<OutputFile> outputFilesBuilder, ImmutableListMultimap.Builder<String, OutputFile> outputFileMapBuilder) 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, outputFilesBuilder); outputFileMapBuilder.put(attribute.getName(), outputFile); } private OutputFile addOutputFile( Label label, EventHandler eventHandler, ImmutableList.Builder<OutputFile> outputFilesBuilder) { 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); outputFilesBuilder.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(); } /** Returns the suffix of target kind for all rules. */ public static String targetKindSuffix() { return " rule"; } }