blob: ded74784dafbff192d83ee3acc8d57a08d424732 [file] [log] [blame]
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.packages;
import static com.google.devtools.build.lib.packages.Types.STRING_LIST;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Interner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.packages.License.DistributionType;
import com.google.devtools.build.lib.packages.License.LicenseParsingException;
import com.google.devtools.build.lib.packages.Type.ConversionException;
import com.google.devtools.build.lib.packages.Type.DictType;
import com.google.devtools.build.lib.packages.Type.LabelClass;
import com.google.devtools.build.lib.packages.Type.ListType;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkValue;
/**
* Collection of data types that are specific to building things, i.e. not inherent to Starlark.
*
* <p>BEFORE YOU ADD A NEW TYPE: See javadoc in {@link Type}.
*/
public final class BuildType {
/**
* The type of a label. Labels are not actually a first-class datatype in the build language, but
* they are so frequently used in the definitions of attributes that it's worth treating them
* specially (and providing support for resolution of relative-labels in the <code>convert()
* </code> method).
*/
@SerializationConstant
public static final Type<Label> LABEL = new LabelType(LabelClass.DEPENDENCY);
/** The type of a dictionary of {@linkplain #LABEL labels}. */
@SerializationConstant
public static final DictType<String, Label> LABEL_DICT_UNARY =
DictType.create(Type.STRING, LABEL);
/** The type of a dictionary keyed by {@linkplain #LABEL labels} with string values. */
@SerializationConstant
public static final DictType<Label, String> LABEL_KEYED_STRING_DICT =
LabelKeyedDictType.create(Type.STRING);
/** The type of a list of {@linkplain #LABEL labels}. */
@SerializationConstant public static final ListType<Label> LABEL_LIST = ListType.create(LABEL);
/**
* This is a label type that does not cause dependencies. It is needed because certain rules want
* to verify the type of a target referenced by one of their attributes, but if there was a
* dependency edge there, it would be a circular dependency.
*/
@SerializationConstant
public static final Type<Label> NODEP_LABEL = new LabelType(LabelClass.NONDEP_REFERENCE);
/** The type of a list of {@linkplain #NODEP_LABEL labels} that do not cause dependencies. */
@SerializationConstant
public static final ListType<Label> NODEP_LABEL_LIST = ListType.create(NODEP_LABEL);
/**
* This is a label type that causes dependencies, but the dependencies are NOT to be configured.
* Does not say anything about whether the attribute of this type is itself configurable.
*
* <p>Without a special type to handle genquery.scope, configuring a genquery target ends up
* configuring the transitive closure of genquery.scope. Since genquery rule implementation loads
* the deps through TransitiveTargetFunction, it doesn't need them to be configured. Preventing
* the dependencies of scope from being configured, lets us save some resources.
*/
@SerializationConstant
public static final Type<Label> GENQUERY_SCOPE_TYPE =
new LabelType(LabelClass.GENQUERY_SCOPE_REFERENCE);
/**
* This is a label type that causes dependencies, but the dependencies are NOT to be configured.
* Does not say anything about whether the attribute of this type is itself configurable.
*/
@SerializationConstant
public static final ListType<Label> GENQUERY_SCOPE_TYPE_LIST =
ListType.create(GENQUERY_SCOPE_TYPE);
/**
* The type of a license. Like Label, licenses aren't first-class, but they're important enough to
* justify early syntax error detection.
*/
@SerializationConstant public static final Type<License> LICENSE = new LicenseType();
/** The type of a single distribution. Only used internally, as a type symbol, not a converter. */
@SerializationConstant
static final Type<DistributionType> DISTRIBUTION =
new Type<DistributionType>() {
@Override
public DistributionType cast(Object value) {
return (DistributionType) value;
}
@Override
public DistributionType convert(Object x, Object what, LabelConverter labelConverter) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public DistributionType getDefaultValue() {
return null;
}
@Override
public void visitLabels(
LabelVisitor visitor, DistributionType value, @Nullable Attribute context) {}
@Override
public String toString() {
return "distribution";
}
};
/**
* The type of a set of distributions. Distributions are not a first-class type, but they do
* warrant early syntax checking.
*/
@SerializationConstant
public static final Type<Set<DistributionType>> DISTRIBUTIONS = new Distributions();
/** The type of an output file, treated as a {@link #LABEL}. */
@SerializationConstant public static final Type<Label> OUTPUT = new OutputType();
/** The type of a list of {@linkplain #OUTPUT outputs}. */
@SerializationConstant public static final ListType<Label> OUTPUT_LIST = ListType.create(OUTPUT);
/** The type of a TriState with values: true (x>0), false (x==0), auto (x<0). */
@SerializationConstant public static final Type<TriState> TRISTATE = new TriStateType();
private BuildType() {
// Do not instantiate
}
/** Returns whether the specified type is a label type or not. */
public static boolean isLabelType(Type<?> type) {
return type.getLabelClass() != LabelClass.NONE;
}
/**
* Variation of {@link Type#convert} that supports selector expressions for configurable
* attributes* (i.e. "{ config1: 'value1_of_orig_type', config2: 'value2_of_orig_type; }"). If x
* is a selector expression, returns a {@link Selector} instance that contains key-mapped entries
* of the native type. Else, returns the native type directly.
*
* <p>The caller is responsible for casting the returned value appropriately.
*/
static <T> Object selectableConvert(Type<T> type, Object x, Object what, LabelConverter context)
throws ConversionException {
if (x instanceof com.google.devtools.build.lib.packages.SelectorList) {
return new SelectorList<>(
((com.google.devtools.build.lib.packages.SelectorList) x).getElements(),
what,
context,
type);
} else {
return type.convert(x, what, context);
}
}
/**
* Converts the build-language-typed {@code buildLangValue} to a native value via {@link
* BuildType#selectableConvert}. Canonicalizes the value's order if it is a {@link List} type and
* {@code attr.isOrderIndependent()} returns {@code true}.
*
* <p>Throws {@link ConversionException} if the conversion fails, or if {@code buildLangValue} is
* a selector expression but {@code attr.isConfigurable()} is {@code false}.
*/
public static Object convertFromBuildLangType(
String ruleClass,
Attribute attr,
Object buildLangValue,
LabelConverter labelConverter,
Interner<ImmutableList<?>> listInterner)
throws ConversionException {
Object converted =
BuildType.selectableConvert(
attr.getType(),
buildLangValue,
new AttributeConversionContext(attr.getName(), ruleClass),
labelConverter);
if ((converted instanceof SelectorList<?>) && !attr.isConfigurable()) {
throw new ConversionException(
String.format("attribute \"%s\" is not configurable", attr.getName()));
}
if (converted instanceof List<?>) {
if (attr.isOrderIndependent()) {
@SuppressWarnings("unchecked")
List<? extends Comparable<?>> list = (List<? extends Comparable<?>>) converted;
converted = Ordering.natural().sortedCopy(list);
}
// It's common for multiple rule instances in the same package to have the same value for some
// attributes. As a concrete example, consider a package having several 'java_test' instances,
// each with the same exact 'tags' attribute value.
converted = listInterner.intern(ImmutableList.copyOf((List<?>) converted));
}
return converted;
}
/** Copies a Starlark SelectorList converting label strings to Label objects. */
private static <T> Object copyAndLiftSelectorList(
Type<T> type,
com.google.devtools.build.lib.packages.SelectorList x,
Object what,
LabelConverter context)
throws ConversionException {
List<Object> elements = x.getElements();
try {
if (elements.size() > 1 && type.concat(ImmutableList.of()) == null) {
throw new ConversionException(
String.format("type '%s' doesn't support select concatenation", type));
}
ImmutableList.Builder<Object> builder = ImmutableList.builder();
for (Object elem : elements) {
ImmutableMap.Builder<Label, Object> newMap = ImmutableMap.builder();
if (elem instanceof SelectorValue) {
for (var entry : ((SelectorValue) elem).getDictionary().entrySet()) {
Label key = LABEL.convert(entry.getKey(), what, context);
newMap.put(
key,
entry.getValue() == Starlark.NONE
? Starlark.NONE
: type.copyAndLiftStarlarkValue(
entry.getValue(), new SelectBranchMessage(what, key), context));
}
builder.add(
new SelectorValue(
newMap.buildKeepingLast(), ((SelectorValue) elem).getNoMatchError()));
} else {
Object directValue = type.copyAndLiftStarlarkValue(elem, what, context);
builder.add(directValue);
}
}
return com.google.devtools.build.lib.packages.SelectorList.of(builder.build());
} catch (EvalException e) {
throw new ConversionException(e.getMessage());
}
}
/**
* Copies a Starlark value to immutable ones and converts label strings to Label objects.
*
* <p>{@code attrOwner} is the name of the rule or macro on which the attribute is defined, e.g.
* "cc_library".
*
* <p>All Starlark values are also type checked.
*
* <p>In comparison to {@link #convertFromBuildLangType} unordered attributes are not
* canonicalized or interned.
*
* <p>Use the function before passing the values to initializers.
*
* @throws ConversionException if the {@code starlarkValue} doesn't match the type of attr or if
* {@code starlarkValue} is a selector expression but {@code attr.isConfigurable()} is {@code
* false}.
*/
public static Object copyAndLiftStarlarkValue(
String attrOwner, Attribute attr, Object starlarkValue, LabelConverter labelConverter)
throws ConversionException {
if (starlarkValue instanceof com.google.devtools.build.lib.packages.SelectorList) {
if (!attr.isConfigurable()) {
throw new ConversionException(
String.format("attribute \"%s\" is not configurable", attr.getName()));
}
return copyAndLiftSelectorList(
attr.getType(),
(com.google.devtools.build.lib.packages.SelectorList) starlarkValue,
new AttributeConversionContext(attr.getName(), attrOwner),
labelConverter);
} else {
return attr.getType()
.copyAndLiftStarlarkValue(
starlarkValue,
new AttributeConversionContext(attr.getName(), attrOwner),
labelConverter);
}
}
/**
* A pair of an attribute name and owner, with a toString that includes both.
*
* <p>This is used to defer stringifying this information until needed for an error message, so as
* to avoid generating unnecessary garbage.
*/
private static class AttributeConversionContext {
private final String attrName;
private final String attrOwner;
/**
* Constructs a new context object from a pair of strings.
*
* @param attrName an attribute name, such as "deps"
* @param attrOwner a rule or macro on which the attribute is defined, e.g. "cc_library"
*/
AttributeConversionContext(String attrName, String attrOwner) {
this.attrName = attrName;
this.attrOwner = attrOwner;
}
@Override
public String toString() {
return String.format("attribute '%s' of '%s'", attrName, attrOwner);
}
}
private static class LabelType extends Type<Label> {
private final LabelClass labelClass;
LabelType(LabelClass labelClass) {
this.labelClass = labelClass;
}
@Override
public Label cast(Object value) {
return (Label) value;
}
@Override
public Label getDefaultValue() {
return null; // Labels have no default value
}
@Override
public void visitLabels(LabelVisitor visitor, Label value, @Nullable Attribute context) {
visitor.visit(value, context);
}
@Override
public String toString() {
return "label";
}
@Override
public LabelClass getLabelClass() {
return labelClass;
}
@Override
public Label convert(Object x, Object what, LabelConverter labelConverter)
throws ConversionException {
if (x instanceof Label) {
return (Label) x;
}
if (!(x instanceof String)) {
throw new ConversionException(Type.STRING, x, what);
}
try {
if (labelConverter == null) {
return Label.parseCanonical((String) x);
}
return labelConverter.convert((String) x);
} catch (LabelSyntaxException e) {
throw new ConversionException(
"invalid label '" + x + "' in " + what + ": " + e.getMessage());
}
}
}
/**
* Dictionary type specialized for label keys, which is able to detect collisions caused by the
* fact that labels have multiple equivalent representations in Starlark code.
*/
private static class LabelKeyedDictType<ValueT> extends DictType<Label, ValueT> {
private LabelKeyedDictType(Type<ValueT> valueType) {
super(LABEL, valueType, LabelClass.DEPENDENCY);
}
static <ValueT> LabelKeyedDictType<ValueT> create(Type<ValueT> valueType) {
Preconditions.checkArgument(
valueType.getLabelClass() == LabelClass.NONE
|| valueType.getLabelClass() == LabelClass.DEPENDENCY,
"Values associated with label keys must not be labels themselves.");
return new LabelKeyedDictType<>(valueType);
}
@Override
public Map<Label, ValueT> convert(Object x, Object what, LabelConverter labelConverter)
throws ConversionException {
Map<Label, ValueT> result = super.convert(x, what, labelConverter);
// The input is known to be a map because super.convert succeeded; otherwise, a
// ConversionException would have been thrown.
Map<?, ?> input = (Map<?, ?>) x;
if (input.size() == result.size()) {
// No collisions found. Exit early.
return result;
}
// Look for collisions in order to produce a nicer error message.
Map<Label, List<Object>> convertedFrom = new LinkedHashMap<>();
for (Object original : input.keySet()) {
Label label = LABEL.convert(original, what, labelConverter);
convertedFrom.computeIfAbsent(label, k -> new ArrayList<>()).add(original);
}
Printer errorMessage = new Printer();
errorMessage.append("duplicate labels");
if (what != null) {
errorMessage.append(" in ").append(what.toString());
}
errorMessage.append(':');
boolean isFirstEntry = true;
for (Map.Entry<Label, List<Object>> entry : convertedFrom.entrySet()) {
if (entry.getValue().size() == 1) {
continue;
}
if (isFirstEntry) {
isFirstEntry = false;
} else {
errorMessage.append(',');
}
errorMessage.append(' ');
errorMessage.append(entry.getKey().getCanonicalForm());
errorMessage.append(" (as ");
errorMessage.repr(entry.getValue());
errorMessage.append(')');
}
throw new ConversionException(errorMessage.toString());
}
}
/**
* Like Label, LicenseType is a derived type, which is declared specially in order to allow syntax
* validation. It represents the licenses, as described in {@link License}.
*/
public static final class LicenseType extends Type<License> {
@Override
public License cast(Object value) {
return (License) value;
}
@Override
public License convert(Object x, Object what, LabelConverter labelConverter)
throws ConversionException {
try {
List<String> licenseStrings = STRING_LIST.convert(x, what);
return License.parseLicense(licenseStrings);
} catch (LicenseParsingException e) {
throw new ConversionException(e.getMessage());
}
}
@Override
public Object copyAndLiftStarlarkValue(
Object x, Object what, @Nullable LabelConverter labelConverter) throws ConversionException {
return STRING_LIST.copyAndLiftStarlarkValue(x, what, labelConverter);
}
@Override
public License getDefaultValue() {
return License.NO_LICENSE;
}
@Override
public void visitLabels(LabelVisitor visitor, License value, @Nullable Attribute context) {}
@Override
public String toString() {
return "license";
}
}
/**
* Like Label, Distributions is a derived type, which is declared specially in order to allow
* syntax validation. It represents the declared distributions of a target, as described in {@link
* License}.
*/
private static final class Distributions extends Type<Set<DistributionType>> {
@SuppressWarnings("unchecked")
@Override
public Set<DistributionType> cast(Object value) {
return (Set<DistributionType>) value;
}
@Override
public Set<DistributionType> convert(Object x, Object what, LabelConverter labelConverter)
throws ConversionException {
try {
List<String> distribStrings = STRING_LIST.convert(x, what);
return License.parseDistributions(distribStrings);
} catch (LicenseParsingException e) {
throw new ConversionException(e.getMessage());
}
}
@Override
public Set<DistributionType> getDefaultValue() {
return Collections.emptySet();
}
@Override
public void visitLabels(
LabelVisitor visitor, Set<DistributionType> value, @Nullable Attribute context) {}
@Override
public String toString() {
return "distributions";
}
@Override
public Type<DistributionType> getListElementType() {
return DISTRIBUTION;
}
}
private static final class OutputType extends Type<Label> {
@Override
public Label cast(Object value) {
return (Label) value;
}
@Nullable
@Override
public Label getDefaultValue() {
return null;
}
@Override
public void visitLabels(LabelVisitor visitor, Label value, @Nullable Attribute context) {
visitor.visit(value, context);
}
@Override
public LabelClass getLabelClass() {
return LabelClass.OUTPUT;
}
@Override
public String toString() {
return "output";
}
@Override
public Label convert(Object x, Object what, LabelConverter labelConverter)
throws ConversionException {
Label result = LABEL.convert(x, what, labelConverter);
if (!result.getPackageIdentifier().equals(labelConverter.getBasePackage())) {
throw new ConversionException("label '" + x + "' is not in the current package");
}
return result;
}
}
/**
* Holds an ordered collection of {@link Selector}s. This is used to support {@code attr =
* rawValue + select(...) + select(...) + ..."} syntax. For consistency's sake, raw values are
* stored as selects with only a default condition.
*/
// TODO(adonovan): merge with packages.Selector{List,Value}.
// We don't need three classes for the same concept.
public static final class SelectorList<T> implements StarlarkValue {
private final Type<T> originalType;
private final List<Selector<T>> elements;
@VisibleForTesting
SelectorList(
List<Object> x, Object what, @Nullable LabelConverter context, Type<T> originalType)
throws ConversionException {
if (x.size() > 1 && originalType.concat(ImmutableList.of()) == null) {
throw new ConversionException(
String.format("type '%s' doesn't support select concatenation", originalType));
}
ImmutableList.Builder<Selector<T>> builder = ImmutableList.builder();
for (Object elem : x) {
if (elem instanceof SelectorValue) {
builder.add(new Selector<>(((SelectorValue) elem).getDictionary(), what,
context, originalType, ((SelectorValue) elem).getNoMatchError()));
} else {
T directValue = originalType.convert(elem, what, context);
builder.add(new Selector<>(ImmutableMap.of(Selector.DEFAULT_CONDITION_KEY, directValue),
what, context, originalType));
}
}
this.originalType = originalType;
this.elements = builder.build();
}
SelectorList(List<Selector<T>> elements, Type<T> originalType) {
this.elements = ImmutableList.copyOf(elements);
this.originalType = originalType;
}
/**
* Returns a syntactically order-preserved list of all values and selectors for this attribute.
*/
public List<Selector<T>> getSelectors() {
return elements;
}
/**
* Returns the native Type for this attribute (i.e. what this would be if it wasn't a selector
* list).
*/
public Type<T> getOriginalType() {
return originalType;
}
/** Returns the labels of all configurability keys across all selects in this expression. */
public Set<Label> getKeyLabels() {
ImmutableSet.Builder<Label> keys = ImmutableSet.builder();
for (Selector<T> selector : elements) {
selector.forEach(
(label, value) -> {
if (!Selector.isDefaultConditionLabel(label)) {
keys.add(label);
}
});
}
return keys.build();
}
@Override
public String toString() {
return Starlark.repr(this);
}
@Override
public void repr(Printer printer) {
// Convert to a lib.packages.SelectorList to guarantee consistency with callers that serialize
// directly on that type.
List<SelectorValue> selectorValueList = new ArrayList<>();
for (Selector<T> element : elements) {
selectorValueList.add(new SelectorValue(element.mapCopy(), element.getNoMatchError()));
}
try {
printer.repr(com.google.devtools.build.lib.packages.SelectorList.of(selectorValueList));
} catch (EvalException e) {
throw new IllegalStateException("this list should have been validated on creation", e);
}
}
}
/** Lazy string message to pass as the {@code what} when converting a select branch value. */
private static final class SelectBranchMessage {
private final Object what;
private final Label key;
SelectBranchMessage(Object what, Label key) {
this.what = what;
this.key = key;
}
@Override
public String toString() {
return String.format("each branch in select expression of %s (including '%s')", what, key);
}
}
/**
* Represents the entries in a single select expression (in the order they were initially
* specified). Contains the configurability pattern (label) and value (objects of the attribute's
* native type) of each entry.
*/
public static final class Selector<T> {
/** Value to use when none of an attribute's selection criteria match. */
@VisibleForTesting
public static final String DEFAULT_CONDITION_KEY = "//conditions:default";
static final Label DEFAULT_CONDITION_LABEL =
Label.parseCanonicalUnchecked(DEFAULT_CONDITION_KEY);
private final Type<T> originalType;
private final Label[] labels;
// Can contain nulls.
private final T[] values;
private final Set<Label> conditionsWithDefaultValues;
private final String noMatchError;
private final int defaultConditionPos;
/** Creates a new Selector using the default error message when no conditions match. */
Selector(
ImmutableMap<?, ?> x, Object what, @Nullable LabelConverter context, Type<T> originalType)
throws ConversionException {
this(x, what, context, originalType, "");
}
/** Creates a new Selector with a custom error message for when no conditions match. */
Selector(
ImmutableMap<?, ?> x,
Object what,
@Nullable LabelConverter context,
Type<T> originalType,
String noMatchError)
throws ConversionException {
this.originalType = originalType;
Label[] labels = new Label[x.size()];
@SuppressWarnings("unchecked")
T[] values = (T[]) new Object[x.size()];
ImmutableSet.Builder<Label> defaultValuesBuilder = ImmutableSet.builder();
int pos = 0;
int defaultConditionPos = -1;
for (Map.Entry<?, ?> entry : x.entrySet()) {
Label key = LABEL.convert(entry.getKey(), what, context);
labels[pos] = key;
T value;
if (entry.getValue() == Starlark.NONE) {
// { "//condition": None } is the same as not setting the value.
value = originalType.getDefaultValue();
defaultValuesBuilder.add(key);
} else {
Object selectBranch = what == null ? null : new SelectBranchMessage(what, key);
value = originalType.convert(entry.getValue(), selectBranch, context);
}
if (key.equals(DEFAULT_CONDITION_LABEL)) {
defaultConditionPos = pos;
}
values[pos] = value;
pos++;
}
this.labels = labels;
this.values = values;
this.noMatchError = noMatchError;
this.conditionsWithDefaultValues = defaultValuesBuilder.build();
this.defaultConditionPos = defaultConditionPos;
}
/**
* Create a new Selector from raw values. Defensive copies of the supplied arrays are <i>not</i>
* made, so it is imperative that they are not modified following construction.
*/
Selector(
Label[] labels,
T[] values,
Type<T> originalType,
String noMatchError,
ImmutableSet<Label> conditionsWithDefaultValues,
int defaultConditionPos) {
this.labels = labels;
this.values = values;
this.originalType = originalType;
this.noMatchError = noMatchError;
this.conditionsWithDefaultValues = conditionsWithDefaultValues;
this.defaultConditionPos = defaultConditionPos;
}
public boolean hasDefault() {
return defaultConditionPos >= 0;
}
/** Returns the value to use when none of the attribute's selection keys match. */
@Nullable
public T getDefault() {
return defaultConditionPos < 0 ? null : values[defaultConditionPos];
}
/**
* Returns a new {@link ArrayList} containing all the values in the entries of this {@link
* Selector}, in the same order they were initially specified.
*
* <p>Prefer using {@link #forEach} since that makes no allocations.
*/
public ArrayList<T> valuesCopy() {
// N.B. We can't use ImmutableList since we can have null values.
ArrayList<T> result = Lists.newArrayListWithCapacity(getNumEntries());
forEach((label, value) -> result.add(value));
return result;
}
/**
* Returns a new {@link LinkedHashMap} representing the branches of this {@link Selector}, in
* the same order they were initially specified.
*
* <p>Prefer using {@link #forEach} since that makes no allocations.
*/
public LinkedHashMap<Label, T> mapCopy() {
// N.B. We can't use ImmutableMap since we can have null values. But we also want to respect
// the ordering of our original map, so we use LinkedHashMap instead of HashMap.
LinkedHashMap<Label, T> result = Maps.newLinkedHashMapWithExpectedSize(getNumEntries());
forEach(result::put);
return result;
}
/** Consumer for {@link #forEach}. */
public interface SelectorEntryConsumer<T> {
void accept(Label conditionLabel, @Nullable T value);
}
/**
* Passes each entry to the provided {@code consumer}, in the same order they were initially
* specified.
*/
public void forEach(SelectorEntryConsumer<T> consumer) {
for (int i = 0; i < labels.length; i++) {
consumer.accept(labels[i], values[i]);
}
}
/** Consumer for {@link #forEachExceptionally}. */
interface ExceptionalSelectorEntryConsumer<T, E1 extends Exception, E2 extends Exception> {
void accept(Label conditionLabel, @Nullable T value) throws E1, E2;
}
/**
* Passes each entry to the provided {@code consumer}, in the same order they were initially
* specified.
*/
public <E1 extends Exception, E2 extends Exception> void forEachExceptionally(
ExceptionalSelectorEntryConsumer<T, E1, E2> consumer) throws E1, E2 {
for (int i = 0; i < labels.length; i++) {
consumer.accept(labels[i], values[i]);
}
}
/** Returns the number of entries. */
public int getNumEntries() {
return labels.length;
}
/**
* Returns the native Type for this attribute (i.e. what this would be if it wasn't a selector
* expression).
*/
public Type<T> getOriginalType() {
return originalType;
}
/**
* Returns true if this selector has the structure: {"//conditions:default": ...}. That means
* all values are always chosen.
*/
public boolean isUnconditional() {
return labels.length == 1 && defaultConditionPos >= 0;
}
/**
* Returns true if an explicit value is set for the given condition, vs. { "//condition": None }
* which means revert to the default.
*/
public boolean isValueSet(Label condition) {
return !conditionsWithDefaultValues.contains(condition);
}
/**
* Returns a custom error message for this select when no condition matches, or an empty string
* if no such message is declared.
*/
public String getNoMatchError() {
return noMatchError;
}
/**
* Returns true for the default condition label, which is not intended to map to an actual
* target.
*/
public static boolean isDefaultConditionLabel(Label label) {
return DEFAULT_CONDITION_LABEL.equals(label);
}
}
/**
* A TriState value is like a boolean attribute whose default value may be distinguished from
* either of the possible explicitly assigned values. TriState attributes may be assigned the
* values 0 (NO), 1 (YES), or None (AUTO). TriState is deprecated; use attr.int(values=[-1, 0, 1])
* instead.
*/
private static final class TriStateType extends Type<TriState> {
@Override
public TriState cast(Object value) {
return (TriState) value;
}
@Override
public TriState getDefaultValue() {
return TriState.AUTO;
}
@Override
public void visitLabels(LabelVisitor visitor, TriState value, @Nullable Attribute context) {}
@Override
public String toString() {
return "tristate";
}
@Override
public TriState convert(Object x, Object what, LabelConverter labelConverter)
throws ConversionException {
if (x instanceof TriState) {
return (TriState) x;
}
if (x instanceof Boolean) {
// TODO(adonovan): re-enable this under flag control; see b/116691720.
// throw new ConversionException(this, x,
// "rule attribute (tristate is being replaced by "
// + "attr.int(values=[-1, 0, 1]), and it no longer accepts Boolean values; "
// + "instead, use 0 or 1, or None for the default)");
return ((Boolean) x) ? TriState.YES : TriState.NO;
}
int xAsInteger = INTEGER.convert(x, what, labelConverter).toIntUnchecked();
if (xAsInteger == -1) {
return TriState.AUTO;
} else if (xAsInteger == 1) {
return TriState.YES;
} else if (xAsInteger == 0) {
return TriState.NO;
}
throw new ConversionException(this, x, "TriState values is not one of [-1, 0, 1]");
}
}
}