blob: fc49047f6d96007bf951620e5494af88b1563462 [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.packages;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper.attributeOrNull;
import static com.google.devtools.build.lib.util.HashCodes.hashObjects;
import com.google.common.base.Preconditions;
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.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
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.PackageIdentifier;
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.NullEventHandler;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.StarlarkImplicitOutputsFunction;
import com.google.devtools.build.lib.packages.Package.ConfigSettingVisibilityPolicy;
import com.google.devtools.build.lib.server.FailureDetails.PackageLoading;
import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.syntax.Location;
/**
* 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>
*/
// Non-final only for mocking in tests. Do not subclass!
public class Rule extends RuleOrMacroInstance implements Target {
/** Label predicate that allows every label. */
public static final Predicate<Label> ALL_LABELS = Predicates.alwaysTrue();
private static final OutputFile[] NO_OUTPUTS = new OutputFile[0];
public static final String IS_EXECUTABLE_ATTRIBUTE_NAME = "$is_executable";
private final Packageoid pkg;
private final RuleClass ruleClass;
private final Location location;
@Nullable private final CallStack.Node interiorCallStack;
/**
* Output files generated by this rule.
*
* <p>To save memory, this field is either {@link #NO_OUTPUTS} for zero outputs, an {@link
* OutputFile} for a single output, or an {@code OutputFile[]} for multiple outputs.
*
* <p>In the case of multiple outputs, all implicit outputs come before any explicit outputs in
* the array.
*
* <p>The order of the implicit outputs is the same as returned by the implicit output function.
* This allows a native rule implementation and native implicit outputs function to agree on the
* index of a given kind of output. The order of explicit outputs preserves the attribute
* iteration order and the order of values in a list attribute; the latter is important so that
* {@code ctx.outputs.some_list} has a well-defined order.
*/
// Initialized by populateOutputFilesInternal().
private Object outputFiles;
Rule(
Packageoid pkg,
Label label,
RuleClass ruleClass,
Location location,
@Nullable CallStack.Node interiorCallStack) {
super(label, ruleClass.getAttributeProvider().getAttributeCount());
this.pkg = checkNotNull(pkg);
this.ruleClass = checkNotNull(ruleClass);
this.location = checkNotNull(location);
this.interiorCallStack = interiorCallStack;
}
@Override
public Packageoid getPackageoid() {
return pkg;
}
@Override
public Package.Metadata getPackageMetadata() {
return pkg.getMetadata();
}
@Override
public Package.Declarations getPackageDeclarations() {
return pkg.getDeclarations();
}
/**
* Implements {@link RuleOrMacroClass#getDeclaringMacro} using {@link Target#getDeclaringMacro}.
*
* <p>This method cannot be called for rules in deserialized packages.
*/
@Override
@Nullable
public MacroInstance getDeclaringMacro() {
return Target.super.getDeclaringMacro();
}
public RuleClass getRuleClassObject() {
return ruleClass;
}
@Override
public String getTargetKind() {
return ruleClass.getTargetKind();
}
/** Returns the class of this rule. (e.g. "cc_library") */
@Override
public String getRuleClass() {
return ruleClass.getName();
}
/**
* 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 constant, but for genrule, it is a property of
* the individual target, derived from the 'output_to_bindir' attribute.
*/
public boolean outputsToBindir() {
return ruleClass.getName().equals("genrule") // this is unfortunate...
? NonconfigurableAttributeMapper.of(this).get("output_to_bindir", Type.BOOLEAN)
: ruleClass.outputsToBindir();
}
/** Returns true if this rule is an analysis test (set by analysis_test = true). */
public boolean isAnalysisTest() {
return ruleClass.isAnalysisTest();
}
/**
* Returns true if this rule has at least one attribute with an analysis test transition. (A
* starlark-defined transition using analysis_test_transition()).
*/
public boolean hasAnalysisTestTransition() {
return ruleClass.hasAnalysisTestTransition();
}
public boolean isBuildSetting() {
return ruleClass.getBuildSetting() != null;
}
/**
* Returns true if this rule is in error.
*
* <p>Examples of rule errors include attributes with missing values or values of the wrong type.
*
* <p>Any error in a package means that all rules in the package are considered to be in error
* (even if they were evaluated prior to the error). This policy is arguably stricter than need
* be, but stopping a build only for some errors but not others creates user confusion.
*/
public boolean containsErrors() {
return pkg.containsErrors();
}
public boolean hasAspects() {
return ruleClass.hasAspects();
}
/**
* Returns true if the given attribute is configurable.
*/
public boolean isConfigurableAttribute(String attributeName) {
// TODO(murali): This method should be property of ruleclass not rule instance.
// Further, this call to AbstractAttributeMapper.isConfigurable is delegated right back
// to this instance!
return AbstractAttributeMapper.isConfigurable(this, attributeName);
}
/**
* 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.getAttributeProvider().getAttributeByNameMaybe(attrName);
}
/**
* Constructs and returns an immutable list containing all the declared output files of this rule.
*
* <p>There are two kinds of outputs. Explicit outputs are declared in attributes of type OUTPUT
* or OUTPUT_LABEL. Implicit outputs are determined by custom rule logic in an "implicit outputs
* function" (either defined natively or in Starlark), and are named following a template pattern
* based on the target's attributes.
*
* <p>All implicit output files (declared in the {@link RuleClass}) are listed first, followed by
* any explicit files (declared via output attributes). Additionally, both implicit and explicit
* outputs will retain the relative order in which they were declared.
*/
public ImmutableList<OutputFile> getOutputFiles() {
return ImmutableList.copyOf(outputFilesArray());
}
/**
* Constructs and returns an immutable list of all the implicit output files of this rule, in the
* order they were declared.
*/
ImmutableList<OutputFile> getImplicitOutputFiles() {
ImmutableList.Builder<OutputFile> result = ImmutableList.builder();
for (OutputFile output : outputFilesArray()) {
if (!output.isImplicit()) {
break;
}
result.add(output);
}
return result.build();
}
/**
* Constructs and returns an immutable multimap of the explicit outputs, from attribute name to
* associated value.
*
* <p>Keys are listed in the same order as attributes. Order of attribute values (outputs in an
* output list) is preserved.
*
* <p>Since this is a multimap, attributes that have no associated outputs are omitted from the
* result.
*/
public ImmutableListMultimap<String, OutputFile> getExplicitOutputFileMap() {
ImmutableListMultimap.Builder<String, OutputFile> result = ImmutableListMultimap.builder();
for (OutputFile output : outputFilesArray()) {
if (!output.isImplicit()) {
result.put(output.getOutputKey(), output);
}
}
return result.build();
}
/**
* Returns a map of the Starlark-defined implicit outputs, from dict key to output file.
*
* <p>If there is no implicit outputs function, or it is a native one, an empty map is returned.
*
* <p>This is not a multimap because Starlark-defined implicit output functions return exactly one
* output per key.
*/
public ImmutableMap<String, OutputFile> getStarlarkImplicitOutputFileMap() {
if (!(ruleClass.getDefaultImplicitOutputsFunction()
instanceof StarlarkImplicitOutputsFunction)) {
return ImmutableMap.of();
}
ImmutableMap.Builder<String, OutputFile> result = ImmutableMap.builder();
for (OutputFile output : outputFilesArray()) {
if (!output.isImplicit()) {
break;
}
result.put(output.getOutputKey(), output);
}
return result.buildOrThrow();
}
private OutputFile[] outputFilesArray() {
return outputFiles instanceof OutputFile outputFile
? new OutputFile[] {outputFile}
: (OutputFile[]) outputFiles;
}
@Override
public Location getLocation() {
return location;
}
/**
* Returns the stack of function calls active when this rule was instantiated.
*
* <p>Requires reconstructing the call stack from a compact representation, so should only be
* called when the full call stack is needed.
*/
public ImmutableList<StarlarkThread.CallStackEntry> reconstructCallStack() {
ImmutableList.Builder<StarlarkThread.CallStackEntry> stack = ImmutableList.builder();
stack.add(StarlarkThread.callStackEntry(StarlarkThread.TOP_LEVEL, location));
for (CallStack.Node node = interiorCallStack; node != null; node = node.next()) {
stack.add(node.toCallStackEntry());
}
return stack.build();
}
@Nullable
CallStack.Node getInteriorCallStack() {
return interiorCallStack;
}
@Override
public Rule getAssociatedRule() {
return this;
}
/*
*******************************************************************
* 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.
*******************************************************************
*/
@Nullable
private String getRelativeLocation() {
// Determining the workspace root only works reliably if both location and label point to files
// in the same package.
// It would be preferable to construct the path from the label itself, but this doesn't work for
// rules created from function calls in a subincluded file, even if both files share a path
// prefix (for example, when //a/package:BUILD subincludes //a/package/with/a/subpackage:BUILD).
// We can revert to that approach once subincludes aren't supported anymore.
//
// TODO(b/151165647): this logic has always been wrong:
// it spuriously matches occurrences of the package name earlier in the path.
String absolutePath = location.toString();
int pos = absolutePath.indexOf(label.getPackageName());
return (pos < 0) ? null : absolutePath.substring(pos);
}
/**
* Returns the value of the attribute with the given index. Returns null, if no such attribute
* exists OR no value was set.
*/
@Override
@Nullable
Object getAttrWithIndex(int attrIndex) {
Object value = getAttrIfStored(attrIndex);
if (value != null) {
return value;
}
Attribute attr = ruleClass.getAttributeProvider().getAttribute(attrIndex);
if (attr.hasComputedDefault()) {
// Frozen rules don't store computed defaults, so get it from the attribute. Mutable rules do
// store computed defaults if they've been populated. If no value is stored for a mutable
// rule, return null here since resolving the default could trigger reads of other attributes
// which have not yet been populated. Note that in this situation returning null does not
// result in a correctness issue, since the value for the attribute is actually a function to
// compute the value.
return isFrozen() ? attr.getDefaultValue(this) : null;
}
if (attr.isMaterializing()) {
checkState(isFrozen(), "Mutable rule missing LateBoundDefault");
return attr.getMaterializer();
}
if (attr.isLateBound()) {
// Frozen rules don't store late bound defaults.
checkState(isFrozen(), "Mutable rule missing LateBoundDefault");
return attr.getLateBoundDefault();
}
return switch (attr.getName()) {
case GENERATOR_FUNCTION -> interiorCallStack != null ? interiorCallStack.functionName() : "";
case GENERATOR_LOCATION -> interiorCallStack != null ? getRelativeLocation() : "";
case GENERATOR_NAME ->
generatorNamePrefixLength > 0 ? getName().substring(0, generatorNamePrefixLength) : "";
default -> attr.getDefaultValue(this);
};
}
@Override
public boolean isRuleInstance() {
return true;
}
@Override
public boolean isRuleCreatedInMacro() {
// TODO(bazel-team): do we really need the `hasStringAttribute(GENERATOR_NAME)` check?
return interiorCallStack != null || hasStringAttribute(GENERATOR_NAME);
}
/** Returns the macro that generated this rule, or an empty string. */
public String getGeneratorFunction() {
Object value = getAttr(GENERATOR_FUNCTION);
if (value instanceof String valString) {
return valString;
}
return "";
}
private boolean hasStringAttribute(String attrName) {
Object value = getAttr(attrName);
if (value instanceof String valString) {
return !valString.isEmpty();
}
return false;
}
/**
* Returns a new list containing all direct dependencies (all types except outputs and nodeps).
*/
public List<Label> getLabels() {
List<Label> labels = new ArrayList<>();
AggregatingAttributeMapper.of(this).visitAllLabels((attribute, label) -> labels.add(label));
return labels;
}
/**
* Returns a sorted set containing all labels that match a given {@link DependencyFilter}, not
* including outputs.
*
* @param filter A dependency filter that determines whether a label should be included in the
* result. {@link DependencyFilter#test} is called with this rule and the attribute that
* contains the label. The label will be contained in the result iff the predicate returns
* {@code true} <em>and</em> the label is not an output.
*/
public ImmutableSortedSet<Label> getSortedLabels(DependencyFilter filter) {
ImmutableSortedSet.Builder<Label> labels = ImmutableSortedSet.naturalOrder();
AggregatingAttributeMapper.of(this)
.visitLabels(filter, (Attribute attribute, Label label) -> labels.add(label));
return labels.build();
}
/**
* Returns a {@link SetMultimap} containing all non-output labels matching a given {@link
* DependencyFilter}, keyed by the corresponding attribute.
*
* <p>Labels that appear in multiple attributes will be mapped from each of their corresponding
* attributes, provided they pass the {@link DependencyFilter}.
*
* @param filter A dependency filter that determines whether a label should be included in the
* result. {@link DependencyFilter#test} is called with this rule and the attribute that
* contains the label. The label will be contained in the result iff the predicate returns
* {@code true} <em>and</em> the label is not an output.
*/
public SetMultimap<Attribute, Label> getTransitions(DependencyFilter filter) {
SetMultimap<Attribute, Label> transitions = HashMultimap.create();
AggregatingAttributeMapper.of(this).visitLabels(filter, transitions::put);
return transitions;
}
/**
* Collects the output files (both implicit and explicit). Must be called before the output
* accessors methods can be used, and must be called only once.
*/
void populateOutputFiles(EventHandler eventHandler, PackageIdentifier pkgId)
throws LabelSyntaxException, InterruptedException {
populateOutputFilesInternal(
eventHandler,
pkgId,
ruleClass.getDefaultImplicitOutputsFunction(),
/* checkLabels= */ true);
}
void populateOutputFilesUnchecked(
TargetDefinitionContext targetDefinitionContext,
ImplicitOutputsFunction implicitOutputsFunction)
throws InterruptedException {
try {
populateOutputFilesInternal(
NullEventHandler.INSTANCE,
targetDefinitionContext.getPackageIdentifier(),
implicitOutputsFunction,
/* checkLabels= */ false);
} catch (LabelSyntaxException e) {
throw new IllegalStateException(e);
}
}
@FunctionalInterface
private interface ExplicitOutputHandler {
void accept(Attribute attribute, Label outputLabel) throws LabelSyntaxException;
}
@FunctionalInterface
private interface ImplicitOutputHandler {
void accept(String outputKey, String outputName);
}
private void populateOutputFilesInternal(
EventHandler eventHandler,
PackageIdentifier pkgId,
ImplicitOutputsFunction implicitOutputsFunction,
boolean checkLabels)
throws LabelSyntaxException, InterruptedException {
Preconditions.checkState(outputFiles == null);
List<OutputFile> outputs = new ArrayList<>();
// Detects collisions where the same output key is used for both an explicit and implicit entry.
HashSet<String> implicitOutputKeys = new HashSet<>();
// We need the implicits to appear before the explicits in the final data structure, so we
// process them first. We check for duplicates while handling the explicits.
//
// Each of these cases has two subcases, so we factor their bodies out into lambdas.
ImplicitOutputHandler implicitOutputHandler =
// outputKey: associated dict key if Starlark-defined, empty string otherwise
// outputName: package-relative path fragment
(outputKey, outputName) -> {
Label label;
if (checkLabels) { // controls label syntax validation only
try {
label = Label.create(pkgId, outputName);
} catch (LabelSyntaxException e) {
reportError(
String.format(
"illegal output file name '%s' in rule %s due to: %s",
outputName, this.label, e.getMessage()),
eventHandler);
return;
}
} else {
label = Label.createUnvalidated(pkgId, outputName);
}
validateOutputLabel(label, eventHandler);
outputs.add(OutputFile.createImplicit(label, this, outputKey));
implicitOutputKeys.add(outputKey);
};
// Populate the implicit outputs.
try {
RawAttributeMapper attributeMap = RawAttributeMapper.of(this);
// TODO(bazel-team): Reconsider the ImplicitOutputsFunction abstraction. It doesn't seem to be
// a good fit if it forces us to downcast in situations like this. It also causes
// getImplicitOutputs() to declare that it throws EvalException (which then has to be
// explicitly disclaimed by the subclass SafeImplicitOutputsFunction).
if (implicitOutputsFunction instanceof StarlarkImplicitOutputsFunction) {
for (Map.Entry<String, String> e :
((StarlarkImplicitOutputsFunction) implicitOutputsFunction)
.calculateOutputs(eventHandler, attributeMap)
.entrySet()) {
implicitOutputHandler.accept(e.getKey(), e.getValue());
}
} else {
for (String out : implicitOutputsFunction.getImplicitOutputs(eventHandler, attributeMap)) {
implicitOutputHandler.accept(/*outputKey=*/ "", out);
}
}
} catch (EvalException e) {
reportError(String.format("In rule %s: %s", label, e.getMessageWithStack()), eventHandler);
}
ExplicitOutputHandler explicitOutputHandler =
(attribute, outputLabel) -> {
String attrName = attribute.getName();
if (implicitOutputKeys.contains(attrName)) {
reportError(
String.format(
"Implicit output key '%s' collides with output attribute name", attrName),
eventHandler);
}
if (checkLabels) {
if (!outputLabel.getPackageIdentifier().equals(pkg.getPackageIdentifier())) {
throw new IllegalStateException(
String.format(
"Label for attribute %s should refer to '%s' but instead refers to '%s'"
+ " (label '%s')",
attribute,
pkg.getMetadata().getName(),
outputLabel.getPackageFragment(),
outputLabel.getName()));
}
if (outputLabel.getName().equals(".")) {
throw new LabelSyntaxException("output file name can't be equal '.'");
}
}
validateOutputLabel(outputLabel, eventHandler);
outputs.add(OutputFile.createExplicit(outputLabel, this, attrName));
};
// Populate the explicit outputs.
NonconfigurableAttributeMapper nonConfigurableAttributes =
NonconfigurableAttributeMapper.of(this);
for (Attribute attribute : ruleClass.getAttributeProvider().getAttributes()) {
String name = attribute.getName();
Type<?> type = attribute.getType();
if (type == BuildType.OUTPUT) {
Label label = nonConfigurableAttributes.get(name, BuildType.OUTPUT);
if (label != null) {
explicitOutputHandler.accept(attribute, label);
}
} else if (type == BuildType.OUTPUT_LIST) {
for (Label label : nonConfigurableAttributes.get(name, BuildType.OUTPUT_LIST)) {
explicitOutputHandler.accept(attribute, label);
}
}
}
if (outputs.isEmpty()) {
outputFiles = NO_OUTPUTS;
} else if (outputs.size() == 1) {
outputFiles = outputs.get(0);
} else {
outputFiles = outputs.toArray(OutputFile[]::new);
}
}
private void validateOutputLabel(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);
}
}
/**
* Marks the rule's package or package piece as in error, and propagates the error message to the
* reporter.
*
* <p>This method may only be called while the rule's package or package piece is being
* constructed.
*/
@Override
void reportError(String message, EventHandler eventHandler) {
eventHandler.handle(Package.error(location, message, PackageLoading.Code.STARLARK_EVAL_ERROR));
pkg.setContainsErrors();
}
private void reportWarning(String message, EventHandler eventHandler) {
eventHandler.handle(Event.warn(location, message));
}
/** Returns a string of the form "cc_binary rule //foo:foo" */
@Override
public String toString() {
return getRuleClass() + " rule " + label;
}
/**
* Implementation of {@link #getRawVisibility} that avoids constructing a {@code RuleVisibility}.
*/
@Nullable
@SuppressWarnings("unchecked")
private List<Label> getRawVisibilityLabels() {
Integer visibilityIndex = ruleClass.getAttributeProvider().getAttributeIndex("visibility");
if (visibilityIndex == null) {
return null;
}
return (List<Label>) getAttrIfStored(visibilityIndex);
}
@Override
@Nullable
public RuleVisibility getRawVisibility() {
List<Label> rawLabels = getRawVisibilityLabels();
// The attribute value was already validated when it was set, so call the unchecked method.
return rawLabels != null ? RuleVisibility.parseUnchecked(rawLabels) : null;
}
/**
* Retrieves the package's default visibility, or for certain rule classes, injects a different
* default visibility.
*/
@Override
public RuleVisibility getDefaultVisibility() {
if (ruleClass.getName().equals("bind")) {
return RuleVisibility.PUBLIC; // bind rules are always public.
}
// Temporary logic to relax config_setting's visibility enforcement while depot migrations set
// visibility settings properly (legacy code may have visibility settings that would break if
// enforced). See https://github.com/bazelbuild/bazel/issues/12669. Ultimately this entire
// conditional should be removed.
if (ruleClass.getName().equals("config_setting")
&& pkg.getMetadata().configSettingVisibilityPolicy()
== ConfigSettingVisibilityPolicy.DEFAULT_PUBLIC) {
return RuleVisibility.PUBLIC; // Default: //visibility:public.
}
return Target.super.getDefaultVisibility();
}
@Override
public Iterable<Label> getVisibilityDependencyLabels() {
List<Label> rawLabels = getRawVisibilityLabels();
if (rawLabels == null) {
return getDefaultVisibility().getDependencyLabels();
}
RuleVisibility constantVisibility = RuleVisibility.parseIfConstant(rawLabels);
if (constantVisibility != null) {
return constantVisibility.getDependencyLabels();
}
// Filter out labels like :__pkg__ and :__subpackages__.
return Iterables.filter(rawLabels, label -> PackageSpecification.fromLabel(label) == null);
}
@Override
public boolean isConfigurable() {
return true;
}
@Override
public License getLicense() {
// New style licenses defined by Starlark rules don't
// have old-style licenses. This is hardcoding the representation
// of new-style rules, but it's in the old-style licensing code path
// and will ultimately be removed.
if (ruleClass.isPackageMetadataRule()) {
return License.NO_LICENSE;
} else if (isAttrDefined("licenses", BuildType.LICENSE)
&& isAttributeValueExplicitlySpecified("licenses")) {
return NonconfigurableAttributeMapper.of(this).get("licenses", BuildType.LICENSE);
} else if (ruleClass.ignoreLicenses()) {
return License.NO_LICENSE;
} else {
return getPackageDeclarations().getPackageArgs().license();
}
}
/** Returns the Set of all tags exhibited by this target. May be empty. */
@Override
public Set<String> getRuleTags() {
Set<String> ruleTags = new LinkedHashSet<>();
for (Attribute attribute : ruleClass.getAttributeProvider().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;
}
/** Returns only the `tags` attribute value. */
public ImmutableList<String> getOnlyTagsAttribute() {
Attribute tagsAttribute = ruleClass.getAttributeProvider().getAttributeByName("tags");
Type<?> attrType = tagsAttribute.getType();
String name = tagsAttribute.getName();
// This enforces the expectation that taggable attributes are non-configurable.
Object value = NonconfigurableAttributeMapper.of(this).get(name, attrType);
return ImmutableList.copyOf(attrType.toTagSet(value, name));
}
@Override
public AttributeProvider getAttributeProvider() {
return ruleClass.getAttributeProvider();
}
@Override
public boolean isRule() {
return true;
}
@Override
@Nullable
public String getDeprecationWarning() {
return attributeOrNull(this, "deprecation", Type.STRING);
}
@Override
public boolean isTestOnly() {
Boolean value = attributeOrNull(this, "testonly", Type.BOOLEAN);
if (value == null) {
return false;
}
return value;
}
@Override
public boolean satisfies(RequiredProviders required) {
return required.isSatisfiedBy(getRuleClassObject().getAdvertisedProviders());
}
@Override
public TestTimeout getTestTimeout() {
return TestTimeout.getTestTimeout(this);
}
@Override
public boolean isForDependencyResolution() {
return getRuleClassObject().isDependencyResolutionRule();
}
@Override
public AdvertisedProviderSet getAdvertisedProviders() {
return getRuleClassObject().getAdvertisedProviders();
}
/**
* Computes labels of additional dependencies that can be provided by aspects that this rule can
* require from its direct dependencies.
*/
public Collection<Label> getAspectLabelsSuperset(DependencyFilter predicate) {
if (!hasAspects()) {
return ImmutableList.of();
}
SetMultimap<Attribute, Label> labels = LinkedHashMultimap.create();
for (Attribute attribute : this.getAttributes()) {
for (Aspect candidateClass : attribute.getAspects(this)) {
AspectDefinition.addAllAttributesOfAspect(labels, candidateClass, predicate);
}
}
return labels.values();
}
/**
* Should this rule instance resolve toolchains?
*
* <p>This may happen for two reasons:
*
* <ol>
* <li>The rule uses toolchains by definition ({@link
* RuleClass.Builder#toolchainResolutionMode(ToolchainResolutionMode)}
* <li>The rule instance has a select() or target_compatible_with attribute, which means it may
* depend on target platform properties that are only provided when toolchain resolution is
* enabled.
* </ol>
*/
public boolean useToolchainResolution() {
return ruleClass.useToolchainResolution(this);
}
public boolean isExecutable() {
if (getRuleClassObject()
.getAttributeProvider()
.hasAttr(IS_EXECUTABLE_ATTRIBUTE_NAME, Type.BOOLEAN)) {
return NonconfigurableAttributeMapper.of(this)
.get(IS_EXECUTABLE_ATTRIBUTE_NAME, Type.BOOLEAN);
}
return false;
}
public RepositoryName getRepository() {
return label.getPackageIdentifier().getRepository();
}
/** Returns the suffix of target kind for all rules. */
public static String targetKindSuffix() {
return " rule";
}
@Override
public TargetData reduceForSerialization() {
return new RuleData(
ruleClass,
getLocation(),
ImmutableSet.copyOf(getRuleTags()),
getLabel(),
getDeprecationWarning(),
isTestOnly(),
getTestTimeout());
}
@VisibleForSerialization // (private) allows RuleDataCodec visibility
static class RuleData implements TargetData {
private final RuleClassData ruleClassData;
private final Location location;
// TODO(b/297857068): this is only used to report TargetCompletion, so it should never be
// read from a deserialized instance. Refine the ConfiguredTargetAndData API and delete this.
private final ImmutableSet<String> ruleTags;
private final Label label;
@Nullable private final String deprecationWarning;
private final boolean isTestOnly;
@Nullable private final TestTimeout testTimeout;
@VisibleForSerialization // (private) allows RuleDataCodec visibility
RuleData(
RuleClassData ruleClassData,
Location location,
ImmutableSet<String> ruleTags,
Label label,
@Nullable String deprecationWarning,
boolean isTestOnly,
@Nullable TestTimeout testTimeout) {
this.ruleClassData = ruleClassData;
this.location = location;
this.ruleTags = ruleTags;
this.label = label;
this.deprecationWarning = deprecationWarning;
this.isTestOnly = isTestOnly;
this.testTimeout = testTimeout;
}
RuleClassData getRuleClassData() {
return ruleClassData;
}
@Override
public String getTargetKind() {
return ruleClassData.getTargetKind();
}
@Override
public Location getLocation() {
return location;
}
@Override
public String getRuleClass() {
return ruleClassData.getName();
}
@Override
public ImmutableSet<String> getRuleTags() {
return ruleTags;
}
@Override
public Label getLabel() {
return label;
}
@Override
public boolean isRule() {
return true;
}
@Override
@Nullable
public String getDeprecationWarning() {
return deprecationWarning;
}
@Override
public boolean satisfies(RequiredProviders required) {
return required.isSatisfiedBy(ruleClassData.getAdvertisedProviders());
}
@Override
public boolean isTestOnly() {
return isTestOnly;
}
@Override
public boolean isForDependencyResolution() {
return ruleClassData.isDependencyResolutionRule();
}
@Override
public AdvertisedProviderSet getAdvertisedProviders() {
return ruleClassData.getAdvertisedProviders();
}
@Override
@Nullable
public TestTimeout getTestTimeout() {
return testTimeout;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof RuleData that)) {
return false;
}
return ruleClassData.equals(that.ruleClassData)
&& location.equals(that.location)
&& label.equals(that.label)
&& Objects.equals(deprecationWarning, that.deprecationWarning)
&& isTestOnly == that.isTestOnly
&& Objects.equals(testTimeout, that.testTimeout);
}
@Override
public int hashCode() {
// Extremely likely equal if this many fields match.
return hashObjects(ruleClassData, location, label);
}
@Override
public String toString() {
return toStringHelper(this)
.add("ruleClassData", ruleClassData)
.add("location", location)
.add("ruleTags", ruleTags)
.add("label", label)
.add("deprecationWarning", deprecationWarning)
.add("isTestOnly", isTestOnly)
.add("testTimeout", testTimeout)
.toString();
}
}
}