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.
*
* The caller is responsible for casting the returned value appropriately.
*/
public static Object selectableConvert(
Type type, Object x, Object what, @Nullable Label context)
throws ConversionException {
if (x instanceof com.google.devtools.build.lib.syntax.SelectorList) {
return new SelectorList(
((com.google.devtools.build.lib.syntax.SelectorList) x).getElements(),
what, context, type);
} else {
return type.convert(x, what, context);
}
}
private static class FilesetEntryType extends
Type {
@Override
public FilesetEntry cast(Object value) {
return (FilesetEntry) value;
}
@Override
public FilesetEntry convert(Object x, Object what, Object context)
throws ConversionException {
if (!(x instanceof FilesetEntry)) {
throw new ConversionException(this, x, what);
}
return (FilesetEntry) x;
}
@Override
public String toString() {
return "FilesetEntry";
}
@Override
public LabelClass getLabelClass() {
return LabelClass.FILESET_ENTRY;
}
@Override
public FilesetEntry getDefaultValue() {
return null;
}
@Override
public void visitLabels(LabelVisitor visitor, Object value) throws InterruptedException {
for (Label label : cast(value).getLabels()) {
visitor.visit(label);
}
}
}
private static class LabelType extends Type {
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, Object value) throws InterruptedException {
visitor.visit(cast(value));
}
@Override
public String toString() {
return "label";
}
@Override
public LabelClass getLabelClass() {
return labelClass;
}
@Override
public Label convert(Object x, Object what, Object context)
throws ConversionException {
if (x instanceof Label) {
return (Label) x;
}
try {
return ((Label) context).getRelative(STRING.convert(x, what, context));
} catch (LabelSyntaxException e) {
throw new ConversionException("invalid label '" + x + "' in "
+ what + ": " + e.getMessage());
}
}
}
/**
* Like Label, LicenseType is a derived type, which is declared specially
* in order to allow syntax validation. It represents the licenses, as
* described in {@ref License}.
*/
public static class LicenseType extends Type {
@Override
public License cast(Object value) {
return (License) value;
}
@Override
public License convert(Object x, Object what, Object context) throws ConversionException {
try {
List licenseStrings = STRING_LIST.convert(x, what);
return License.parseLicense(licenseStrings);
} catch (LicenseParsingException e) {
throw new ConversionException(e.getMessage());
}
}
@Override
public License getDefaultValue() {
return License.NO_LICENSE;
}
@Override
public void visitLabels(LabelVisitor visitor, Object value) {
}
@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 {@ref License}.
*/
private static class Distributions extends
Type> {
@SuppressWarnings("unchecked")
@Override
public Set cast(Object value) {
return (Set) value;
}
@Override
public Set convert(Object x, Object what, Object context)
throws ConversionException {
try {
List distribStrings = STRING_LIST.convert(x, what);
return License.parseDistributions(distribStrings);
} catch (LicenseParsingException e) {
throw new ConversionException(e.getMessage());
}
}
@Override
public Set getDefaultValue() {
return Collections.emptySet();
}
@Override
public void visitLabels(LabelVisitor visitor, Object value) {
}
@Override
public String toString() {
return "distributions";
}
@Override
public Type getListElementType() {
return DISTRIBUTION;
}
}
private static class OutputType extends Type {
@Override
public Label cast(Object value) {
return (Label) value;
}
@Override
public Label getDefaultValue() {
return null;
}
@Override
public void visitLabels(LabelVisitor visitor, Object value) throws InterruptedException {
visitor.visit(cast(value));
}
@Override
public LabelClass getLabelClass() {
return LabelClass.OUTPUT;
}
@Override
public String toString() {
return "output";
}
@Override
public Label convert(Object x, Object what, Object context)
throws ConversionException {
String value;
try {
value = STRING.convert(x, what, context);
} catch (ConversionException e) {
throw new ConversionException(this, x, what);
}
try {
// Enforce value is relative to the context.
Label currentRule = (Label) context;
Label result = currentRule.getRelative(value);
if (!result.getPackageIdentifier().equals(currentRule.getPackageIdentifier())) {
throw new ConversionException("label '" + value + "' is not in the current package");
}
return result;
} catch (LabelSyntaxException e) {
throw new ConversionException(
"illegal output file name '" + value + "' in rule " + context + ": "
+ e.getMessage());
}
}
}
/**
* 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 {
private final Type originalType;
private final List> elements;
@VisibleForTesting
SelectorList(List x, Object what, @Nullable Label context,
Type 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> 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> elements, Type 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> 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 getOriginalType() {
return originalType;
}
/**
* Returns the labels of all configurability keys across all selects in this expression.
*/
public Set getKeyLabels() {
ImmutableSet.Builder keys = ImmutableSet.builder();
for (Selector selector : getSelectors()) {
for (Label label : selector.getEntries().keySet()) {
if (!Selector.isReservedLabel(label)) {
keys.add(label);
}
}
}
return keys.build();
}
}
/**
* Special Type that represents a selector expression for configurable attributes. Holds a
* mapping of {@code } entries, where keys are configurability patterns and values are
* objects of the attribute's native Type.
*/
public static final class Selector {
/** Value to use when none of an attribute's selection criteria match. */
@VisibleForTesting
public static final String DEFAULT_CONDITION_KEY = "//conditions:default";
public static final Label DEFAULT_CONDITION_LABEL =
Label.parseAbsoluteUnchecked(DEFAULT_CONDITION_KEY);
private final Type originalType;
// Can hold null values, underlying implementation should be ordered.
private final Map map;
private final Set conditionsWithDefaultValues;
private final String noMatchError;
private final boolean hasDefaultCondition;
/**
* Creates a new Selector using the default error message when no conditions match.
*/
Selector(ImmutableMap, ?> x, Object what, @Nullable Label context, Type 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 Label context, Type originalType,
String noMatchError) throws ConversionException {
this.originalType = originalType;
LinkedHashMap result = new LinkedHashMap<>();
ImmutableSet.Builder defaultValuesBuilder = ImmutableSet.builder();
boolean foundDefaultCondition = false;
for (Entry, ?> entry : x.entrySet()) {
Label key = LABEL.convert(entry.getKey(), what, context);
if (key.equals(DEFAULT_CONDITION_LABEL)) {
foundDefaultCondition = true;
}
if (entry.getValue() == Runtime.NONE) {
// { "//condition": None } is the same as not setting the value.
result.put(key, originalType.getDefaultValue());
defaultValuesBuilder.add(key);
} else {
result.put(key, originalType.convert(entry.getValue(), what, context));
}
}
this.map = Collections.unmodifiableMap(result);
this.noMatchError = noMatchError;
this.conditionsWithDefaultValues = defaultValuesBuilder.build();
this.hasDefaultCondition = foundDefaultCondition;
}
/**
* Create a new Selector from raw values. A defensive copy of the supplied map is not
* made, so it imperative that it is not modified following construction.
*/
Selector(
LinkedHashMap map,
Type originalType,
String noMatchError,
ImmutableSet conditionsWithDefaultValues,
boolean hasDefaultCondition) {
this.originalType = originalType;
this.map = Collections.unmodifiableMap(map);
this.noMatchError = noMatchError;
this.conditionsWithDefaultValues = conditionsWithDefaultValues;
this.hasDefaultCondition = hasDefaultCondition;
}
/**
* Returns the selector's (configurability pattern --gt; matching values) map.
*
* Entries in this map retain the order of the entries in the map provided to the {@link
* #Selector} constructor.
*/
public Map getEntries() {
return map;
}
/**
* Returns the value to use when none of the attribute's selection keys match.
*/
public T getDefault() {
return map.get(DEFAULT_CONDITION_LABEL);
}
/**
* Returns whether or not this selector has a default condition.
*/
public boolean hasDefault() {
return hasDefaultCondition;
}
/**
* Returns the native Type for this attribute (i.e. what this would be if it wasn't a
* selector expression).
*/
public Type getOriginalType() {
return originalType;
}
/**
* Returns true if this selector has the structure: {"//conditions:default": ...}. That means
* all values are always chosen.
*/
public boolean isUnconditional() {
return map.size() == 1 && hasDefaultCondition;
}
/**
* 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 labels that are "reserved selector key words" and not intended to
* map to actual targets.
*/
public static boolean isReservedLabel(Label label) {
return DEFAULT_CONDITION_LABEL.equals(label);
}
}
/**
* Tristate values are needed for cases where user intent matters.
*
* Tristate values are not explicitly interchangeable with booleans and are
* handled explicitly as TriStates. Prefer Booleans with default values where
* possible. The main use case for TriState values is when a Rule's behavior
* must interact with a Flag value in a complicated way.
*/
private static class TriStateType extends Type {
@Override
public TriState cast(Object value) {
return (TriState) value;
}
@Override
public TriState getDefaultValue() {
return TriState.AUTO;
}
@Override
public void visitLabels(LabelVisitor visitor, Object value) {
}
@Override
public String toString() {
return "tristate";
}
// Like BooleanType, this must handle integers as well.
@Override
public TriState convert(Object x, Object what, Object context)
throws ConversionException {
if (x instanceof TriState) {
return (TriState) x;
}
if (x instanceof Boolean) {
return ((Boolean) x) ? TriState.YES : TriState.NO;
}
Integer xAsInteger = INTEGER.convert(x, what, context);
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]");
}
}
}