Configurable attributes: support embeddable selects. With this
change, the following syntax:
deps = [':always'] + select({':a': [':adep'], ':b': [':bdep']})
or
deps = select({':a': [':adep'], ':b': [':bdep']})
+ select({':c': [':cdep'], ':d': [':ddep']})
works.
--
MOS_MIGRATED_REVID=91016337
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AbstractAttributeMapper.java b/src/main/java/com/google/devtools/build/lib/packages/AbstractAttributeMapper.java
index f3875b6..544435a 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AbstractAttributeMapper.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AbstractAttributeMapper.java
@@ -161,29 +161,30 @@
protected abstract <T> Iterable<T> visitAttribute(String attributeName, Type<T> type);
/**
- * Returns a {@link Type.Selector} for the given attribute if the attribute is configurable
+ * Returns a {@link Type.SelectorList} for the given attribute if the attribute is configurable
* for this rule, null otherwise.
*
- * @return a {@link Type.Selector} if the attribute takes the form
+ * @return a {@link Type.SelectorList} if the attribute takes the form
* "attrName = { 'a': value1_of_type_T, 'b': value2_of_type_T }") for this rule, null
* if it takes the form "attrName = value_of_type_T", null if it doesn't exist
* @throws IllegalArgumentException if the attribute is configurable but of the wrong type
*/
@Nullable
- protected <T> Type.Selector<T> getSelector(String attributeName, Type<T> type) {
+ @SuppressWarnings("unchecked")
+ protected <T> Type.SelectorList<T> getSelectorList(String attributeName, Type<T> type) {
Integer index = ruleClass.getAttributeIndex(attributeName);
if (index == null) {
return null;
}
Object attrValue = attributes.getAttributeValue(index);
- if (!(attrValue instanceof Type.Selector<?>)) {
+ if (!(attrValue instanceof Type.SelectorList)) {
return null;
}
- if (((Type.Selector<?>) attrValue).getOriginalType() != type) {
+ if (((Type.SelectorList) attrValue).getOriginalType() != type) {
throw new IllegalArgumentException("Attribute " + attributeName
+ " is not of type " + type + " in rule " + ruleLabel);
}
- return (Type.Selector<T>) attrValue;
+ return (Type.SelectorList<T>) attrValue;
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AggregatingAttributeMapper.java b/src/main/java/com/google/devtools/build/lib/packages/AggregatingAttributeMapper.java
index 682d2dd..b51b195 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AggregatingAttributeMapper.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AggregatingAttributeMapper.java
@@ -65,12 +65,10 @@
super.visitLabels(observer);
for (String attrName : getAttributeNames()) {
Attribute attribute = getAttributeDefinition(attrName);
- Type.Selector<?> selector = getSelector(attrName, attribute.getType());
- if (selector != null) {
- for (Label configLabel : selector.getEntries().keySet()) {
- if (!Type.Selector.isReservedLabel(configLabel)) {
- observer.acceptLabelAttribute(configLabel, attribute);
- }
+ Type.SelectorList<?> selectorList = getSelectorList(attrName, attribute.getType());
+ if (selectorList != null) {
+ for (Label configLabel : selectorList.getKeyLabels()) {
+ observer.acceptLabelAttribute(configLabel, attribute);
}
}
}
@@ -82,12 +80,10 @@
@Override
public <T> Iterable<T> visitAttribute(String attributeName, Type<T> type) {
// If this attribute value is configurable, visit all possible values.
- Type.Selector<T> selector = getSelector(attributeName, type);
- if (selector != null) {
+ Type.SelectorList<T> selectorList = getSelectorList(attributeName, type);
+ if (selectorList != null) {
ImmutableList.Builder<T> builder = ImmutableList.builder();
- for (Map.Entry<Label, T> entry : selector.getEntries().entrySet()) {
- builder.add(entry.getValue());
- }
+ visitConfigurableAttribute(selectorList.getSelectors(), type, null, builder);
return builder.build();
}
@@ -116,6 +112,33 @@
}
/**
+ * Determines all possible values a configurable attribute can take and places each one into
+ * valuesBuilder.
+ */
+ private <T> void visitConfigurableAttribute(List<Type.Selector<T>> selectors, Type<T> type,
+ T currentValueSoFar, ImmutableList.Builder<T> valuesBuilder) {
+
+ // TODO(bazel-team): minimize or eliminate uses of this interface. It necessarily grows
+ // exponentially with the number of selects in the attribute. Is that always necessary?
+ // For example, dependency resolution just needs to know every possible label an attribute
+ // might reference, but it doesn't need to know the exact combination of labels that make
+ // up a value.
+ if (selectors.isEmpty()) {
+ valuesBuilder.add(Preconditions.checkNotNull(currentValueSoFar));
+ } else {
+ Type.Selector<T> firstSelector = selectors.get(0);
+ List<Type.Selector<T>> remainingSelectors = selectors.subList(1, selectors.size());
+ for (T branchedValue : firstSelector.getEntries().values()) {
+ visitConfigurableAttribute(remainingSelectors, type,
+ currentValueSoFar == null
+ ? branchedValue
+ : type.concat(ImmutableList.of(currentValueSoFar, branchedValue)),
+ valuesBuilder);
+ }
+ }
+ }
+
+ /**
* Given (possibly configurable) attributes that a computed default depends on, creates an
* {attrName -> attrValue} map for every possible combination of those attribute values and
* returns a list of all the maps. This defines the complete dependency space that can affect
diff --git a/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java
index 57d9865..63ada93 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java
@@ -37,6 +37,7 @@
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.Function;
import com.google.devtools.build.lib.syntax.MixedModeFunction;
+import com.google.devtools.build.lib.syntax.SelectorList;
import com.google.devtools.build.lib.syntax.SelectorValue;
import com.google.devtools.build.lib.syntax.SkylarkBuiltin;
import com.google.devtools.build.lib.syntax.SkylarkBuiltin.Param;
@@ -829,7 +830,7 @@
throw new EvalException(ast.getLocation(),
"select({...}) argument isn't a dictionary");
}
- return new SelectorValue((Map<?, ?>) dict);
+ return SelectorList.of(new SelectorValue((Map<?, ?>) dict));
}
};
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RawAttributeMapper.java b/src/main/java/com/google/devtools/build/lib/packages/RawAttributeMapper.java
index 7de23d7..64c22bf 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RawAttributeMapper.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RawAttributeMapper.java
@@ -71,10 +71,11 @@
return get(attributeName, type);
}
- Type.Selector<List<T>> selector = getSelector(attributeName, type);
ImmutableSet.Builder<T> mergedValues = ImmutableSet.builder();
- for (List<T> configuredList : selector.getEntries().values()) {
- mergedValues.addAll(configuredList);
+ for (Type.Selector<List<T>> selector : getSelectorList(attributeName, type).getSelectors()) {
+ for (List<T> configuredList : selector.getEntries().values()) {
+ mergedValues.addAll(configuredList);
+ }
}
return mergedValues.build();
}
@@ -84,7 +85,7 @@
* otherwise.
*/
public <T> boolean isConfigurable(String attributeName, Type<T> type) {
- return getSelector(attributeName, type) != null;
+ return getSelectorList(attributeName, type) != null;
}
/**
@@ -92,7 +93,14 @@
* keys. Else returns an empty list.
*/
public <T> Iterable<Label> getConfigurabilityKeys(String attributeName, Type<T> type) {
- Type.Selector<T> selector = getSelector(attributeName, type);
- return selector == null ? ImmutableList.<Label>of() : selector.getEntries().keySet();
+ Type.SelectorList<T> selectorList = getSelectorList(attributeName, type);
+ if (selectorList == null) {
+ return ImmutableList.of();
+ }
+ ImmutableList.Builder<Label> builder = ImmutableList.builder();
+ for (Type.Selector<T> selector : selectorList.getSelectors()) {
+ builder.addAll(selector.getEntries().keySet());
+ }
+ return builder.build();
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index d265a44..4521b33 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -1278,13 +1278,9 @@
Set<Label> configLabels = new LinkedHashSet<>();
for (Attribute attr : rule.getAttributes()) {
- Type.Selector<?> selector = attributes.getSelector(attr.getName(), attr.getType());
- if (selector != null) {
- for (Label label : selector.getEntries().keySet()) {
- if (!Type.Selector.isReservedLabel(label)) {
- configLabels.add(label);
- }
- }
+ Type.SelectorList<?> selectors = attributes.getSelectorList(attr.getName(), attr.getType());
+ if (selectors != null) {
+ configLabels.addAll(selectors.getKeyLabels());
}
}
@@ -1444,7 +1440,7 @@
String what = "attribute '" + attrName + "' in '" + name + "' rule";
converted = attr.getType().selectableConvert(attrVal, what, rule.getLabel());
- if ((converted instanceof Type.Selector<?>) && !attr.isConfigurable()) {
+ if ((converted instanceof Type.SelectorList<?>) && !attr.isConfigurable()) {
rule.reportError(rule.getLabel() + ": attribute \"" + attr.getName()
+ "\" is not configurable", eventHandler);
return null;
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Type.java b/src/main/java/com/google/devtools/build/lib/packages/Type.java
index e869096..e197bb5 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Type.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Type.java
@@ -15,6 +15,7 @@
package com.google.devtools.build.lib.packages;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -131,10 +132,13 @@
*/
public Object selectableConvert(Object x, String what, @Nullable Label currentRule)
throws ConversionException {
- if (x instanceof SelectorValue) {
- return new Selector<T>(((SelectorValue) x).getDictionary(), what, currentRule, this);
+ if (x instanceof com.google.devtools.build.lib.syntax.SelectorList) {
+ return new SelectorList<T>(
+ ((com.google.devtools.build.lib.syntax.SelectorList) x).getElements(),
+ what, currentRule, this);
+ } else {
+ return convert(x, what, currentRule);
}
- return convert(x, what, currentRule);
}
public abstract T cast(Object value);
@@ -167,6 +171,14 @@
private static final Iterable<Label> NO_LABELS_HERE = ImmutableList.of();
/**
+ * Implementation of concatenation for this type (e.g. "val1 + val2"). Returns null to
+ * designate concatenation isn't supported.
+ */
+ public T concat(Iterable<T> elements) {
+ return null;
+ }
+
+ /**
* Converts an initialized Type object into a tag set representation.
* This operation is only valid for certain sub-Types which are guaranteed
* to be properly initialized.
@@ -426,6 +438,15 @@
}
return (Integer) x;
}
+
+ @Override
+ public Integer concat(Iterable<Integer> elements) {
+ int ans = 0;
+ for (Integer elem : elements) {
+ ans += elem;
+ }
+ return Integer.valueOf(ans);
+ }
}
private static class BooleanType extends Type<Boolean> {
@@ -560,6 +581,11 @@
return StringCanonicalizer.intern((String) x);
}
+ @Override
+ public String concat(Iterable<String> elements) {
+ return Joiner.on("").join(elements);
+ }
+
/**
* A String is representable as a set containing its value.
*/
@@ -771,35 +797,35 @@
/**
* A type to support dictionary attributes.
*/
- public static class DictType<KEY, VALUE> extends Type<Map<KEY, VALUE>> {
+ public static class DictType<KeyT, ValueT> extends Type<Map<KeyT, ValueT>> {
- private final Type<KEY> keyType;
- private final Type<VALUE> valueType;
+ private final Type<KeyT> keyType;
+ private final Type<ValueT> valueType;
- private final Map<KEY, VALUE> empty = ImmutableMap.of();
+ private final Map<KeyT, ValueT> empty = ImmutableMap.of();
private static <KEY, VALUE> DictType<KEY, VALUE> create(
Type<KEY> keyType, Type<VALUE> valueType) {
return new DictType<>(keyType, valueType);
}
- private DictType(Type<KEY> keyType, Type<VALUE> valueType) {
+ private DictType(Type<KeyT> keyType, Type<ValueT> valueType) {
this.keyType = keyType;
this.valueType = valueType;
}
- public Type<KEY> getKeyType() {
+ public Type<KeyT> getKeyType() {
return keyType;
}
- public Type<VALUE> getValueType() {
+ public Type<ValueT> getValueType() {
return valueType;
}
@SuppressWarnings("unchecked")
@Override
- public Map<KEY, VALUE> cast(Object value) {
- return (Map<KEY, VALUE>) value;
+ public Map<KeyT, ValueT> cast(Object value) {
+ return (Map<KeyT, ValueT>) value;
}
@Override
@@ -808,13 +834,13 @@
}
@Override
- public Map<KEY, VALUE> convert(Object x, String what, Label currentRule)
+ public Map<KeyT, ValueT> convert(Object x, String what, Label currentRule)
throws ConversionException {
if (!(x instanceof Map<?, ?>)) {
throw new ConversionException(String.format(
"Expected a map for dictionary but got a %s", x.getClass().getName()));
}
- ImmutableMap.Builder<KEY, VALUE> result = ImmutableMap.builder();
+ ImmutableMap.Builder<KeyT, ValueT> result = ImmutableMap.builder();
Map<?, ?> o = (Map<?, ?>) x;
for (Entry<?, ?> elem : o.entrySet()) {
result.put(
@@ -825,14 +851,14 @@
}
@Override
- public Map<KEY, VALUE> getDefaultValue() {
+ public Map<KeyT, ValueT> getDefaultValue() {
return empty;
}
@Override
public Iterable<Label> getLabels(Object value) {
ImmutableList.Builder<Label> labels = ImmutableList.builder();
- for (Map.Entry<KEY, VALUE> entry : cast(value).entrySet()) {
+ for (Map.Entry<KeyT, ValueT> entry : cast(value).entrySet()) {
labels.addAll(keyType.getLabels(entry.getKey()));
labels.addAll(valueType.getLabels(entry.getValue()));
}
@@ -841,40 +867,40 @@
}
/** A type for lists of a given element type */
- public static class ListType<ELEM> extends Type<List<ELEM>> {
+ public static class ListType<ElemT> extends Type<List<ElemT>> {
- private final Type<ELEM> elemType;
+ private final Type<ElemT> elemType;
- private final List<ELEM> empty = ImmutableList.of();
+ private final List<ElemT> empty = ImmutableList.of();
private static <ELEM> ListType<ELEM> create(Type<ELEM> elemType) {
return new ListType<>(elemType);
}
- private ListType(Type<ELEM> elemType) {
+ private ListType(Type<ElemT> elemType) {
this.elemType = elemType;
}
@SuppressWarnings("unchecked")
@Override
- public List<ELEM> cast(Object value) {
- return (List<ELEM>) value;
+ public List<ElemT> cast(Object value) {
+ return (List<ElemT>) value;
}
@Override
- public Type<ELEM> getListElementType() {
+ public Type<ElemT> getListElementType() {
return elemType;
}
@Override
- public List<ELEM> getDefaultValue() {
+ public List<ElemT> getDefaultValue() {
return empty;
}
@Override
public Iterable<Label> getLabels(Object value) {
ImmutableList.Builder<Label> labels = ImmutableList.builder();
- for (ELEM entry : cast(value)) {
+ for (ElemT entry : cast(value)) {
labels.addAll(elemType.getLabels(entry));
}
return labels.build();
@@ -886,15 +912,15 @@
}
@Override
- public List<ELEM> convert(Object x, String what, Label currentRule)
+ public List<ElemT> convert(Object x, String what, Label currentRule)
throws ConversionException {
if (!(x instanceof Iterable<?>)) {
throw new ConversionException(this, x, what);
}
- List<ELEM> result = new ArrayList<>();
+ List<ElemT> result = new ArrayList<>();
int index = 0;
for (Object elem : (Iterable<?>) x) {
- ELEM converted = elemType.convert(elem, "element " + index + " of " + what, currentRule);
+ ElemT converted = elemType.convert(elem, "element " + index + " of " + what, currentRule);
if (converted != null) {
result.add(converted);
} else {
@@ -913,6 +939,15 @@
}
}
+ @Override
+ public List<ElemT> concat(Iterable<List<ElemT>> elements) {
+ ImmutableList.Builder<ElemT> builder = ImmutableList.builder();
+ for (List<ElemT> list : elements) {
+ builder.addAll(list);
+ }
+ return builder.build();
+ }
+
/**
* A list is representable as a tag set as the contents of itself expressed
* as Strings. So a {@code List<String>} is effectively converted to a {@code Set<String>}.
@@ -925,8 +960,8 @@
}
Set<String> tags = new LinkedHashSet<>();
@SuppressWarnings("unchecked")
- List<ELEM> itemsAsListofElem = (List<ELEM>) items;
- for (ELEM element : itemsAsListofElem) {
+ List<ElemT> itemsAsListofElem = (List<ElemT>) items;
+ for (ElemT element : itemsAsListofElem) {
tags.add(element.toString());
}
return tags;
@@ -978,7 +1013,6 @@
* objects of the attribute's native Type.
*/
public static final class Selector<T> {
-
private final Type<T> originalType;
private final Map<Label, T> map;
private final Label defaultConditionLabel;
@@ -1001,7 +1035,6 @@
throw new IllegalStateException(DEFAULT_CONDITION_KEY + " is not a valid label");
}
-
this.originalType = originalType;
Map<Label, T> result = Maps.newLinkedHashMap();
boolean foundDefaultCondition = false;
@@ -1053,4 +1086,67 @@
return label.toString().equals(DEFAULT_CONDITION_KEY);
}
}
+
+ /**
+ * 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.
+ */
+ public static final class SelectorList<T> {
+ private final Type<T> originalType;
+ private final List<Selector<T>> elements;
+
+ @VisibleForTesting
+ SelectorList(List<Object> x, String what, @Nullable Label currentRule,
+ Type<T> originalType) throws ConversionException {
+ if (x.size() > 1 && originalType.concat(ImmutableList.<T>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<T>(((SelectorValue) elem).getDictionary(), what,
+ currentRule, originalType));
+ } else {
+ T directValue = originalType.convert(elem, what, currentRule);
+ builder.add(new Selector<T>(ImmutableMap.of(Selector.DEFAULT_CONDITION_KEY, directValue),
+ what, currentRule, originalType));
+ }
+ }
+ this.originalType = originalType;
+ this.elements = builder.build();
+ }
+
+ /**
+ * 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 : getSelectors()) {
+ for (Label label : selector.getEntries().keySet()) {
+ if (!Selector.isReservedLabel(label)) {
+ keys.add(label);
+ }
+ }
+ }
+ return keys.build();
+ }
+ }
}