blob: 2fad85146845be7c6ccac641d04846284e6bef3b [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.devtools.build.lib.packages.BuildType.DISTRIBUTIONS;
import static com.google.devtools.build.lib.packages.BuildType.FILESET_ENTRY_LIST;
import static com.google.devtools.build.lib.packages.BuildType.LABEL;
import static com.google.devtools.build.lib.packages.BuildType.LABEL_DICT_UNARY;
import static com.google.devtools.build.lib.packages.BuildType.LABEL_KEYED_STRING_DICT;
import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
import static com.google.devtools.build.lib.packages.BuildType.LICENSE;
import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL;
import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL_LIST;
import static com.google.devtools.build.lib.packages.BuildType.OUTPUT;
import static com.google.devtools.build.lib.packages.BuildType.OUTPUT_LIST;
import static com.google.devtools.build.lib.packages.BuildType.TRISTATE;
import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
import static com.google.devtools.build.lib.packages.Type.INTEGER;
import static com.google.devtools.build.lib.packages.Type.INTEGER_LIST;
import static com.google.devtools.build.lib.packages.Type.STRING;
import static com.google.devtools.build.lib.packages.Type.STRING_DICT;
import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
import static com.google.devtools.build.lib.packages.Type.STRING_LIST_DICT;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.BuildType.Selector;
import com.google.devtools.build.lib.packages.BuildType.SelectorList;
import com.google.devtools.build.lib.query2.proto.proto2api.Build;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.Attribute.Discriminator;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.Attribute.SelectorEntry;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.Attribute.Tristate;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.LabelDictUnaryEntry;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.LabelKeyedStringDictEntry;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.LabelListDictEntry;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.StringDictEntry;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.StringListDictEntry;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/** Common utilities for serializing {@link Attribute}s as protocol buffers. */
public class AttributeFormatter {
private static final ImmutableSet<Type<?>> depTypes =
ImmutableSet.<Type<?>>of(
STRING,
LABEL,
OUTPUT,
STRING_LIST,
LABEL_LIST,
LABEL_DICT_UNARY,
LABEL_KEYED_STRING_DICT,
OUTPUT_LIST,
DISTRIBUTIONS);
private static final ImmutableSet<Type<?>> noDepTypes =
ImmutableSet.<Type<?>>of(NODEP_LABEL_LIST, NODEP_LABEL);
private AttributeFormatter() {}
/**
* Convert attribute value to proto representation.
*
* <p>If {@param value} is null, only the {@code name}, {@code explicitlySpecified}, {@code
* nodep} (if applicable), and {@code type} fields will be included in the proto message.
*
* <p>If {@param encodeBooleanAndTriStateAsIntegerAndString} is true then boolean and tristate
* values are also encoded as integers and strings.
*/
public static Build.Attribute getAttributeProto(
Attribute attr,
@Nullable Object value,
boolean explicitlySpecified,
boolean encodeBooleanAndTriStateAsIntegerAndString) {
return getAttributeProto(
attr.getName(),
attr.getType(),
value,
explicitlySpecified,
encodeBooleanAndTriStateAsIntegerAndString);
}
private static Build.Attribute getAttributeProto(
String name,
Type<?> type,
@Nullable Object value,
boolean explicitlySpecified,
boolean encodeBooleanAndTriStateAsIntegerAndString) {
Build.Attribute.Builder attrPb = Build.Attribute.newBuilder();
attrPb.setName(name);
attrPb.setExplicitlySpecified(explicitlySpecified);
maybeSetNoDep(type, attrPb);
if (value instanceof SelectorList<?>) {
attrPb.setType(Discriminator.SELECTOR_LIST);
writeSelectorListToBuilder(attrPb, type, (SelectorList<?>) value);
} else {
attrPb.setType(ProtoUtils.getDiscriminatorFromType(type));
if (value != null) {
AttributeBuilderAdapter adapter =
new AttributeBuilderAdapter(attrPb, encodeBooleanAndTriStateAsIntegerAndString);
writeAttributeValueToBuilder(adapter, type, value);
}
}
return attrPb.build();
}
private static void maybeSetNoDep(Type<?> type, Build.Attribute.Builder attrPb) {
if (depTypes.contains(type)) {
attrPb.setNodep(false);
} else if (noDepTypes.contains(type)) {
attrPb.setNodep(true);
}
}
private static void writeSelectorListToBuilder(
Build.Attribute.Builder attrPb,
Type<?> type,
SelectorList<?> selectorList) {
Build.Attribute.SelectorList.Builder selectorListBuilder =
Build.Attribute.SelectorList.newBuilder();
selectorListBuilder.setType(ProtoUtils.getDiscriminatorFromType(type));
for (Selector<?> selector : selectorList.getSelectors()) {
Build.Attribute.Selector.Builder selectorBuilder = Build.Attribute.Selector.newBuilder()
.setNoMatchError(selector.getNoMatchError())
.setHasDefaultValue(selector.hasDefault());
// Note that the order of entries returned by selector.getEntries is stable. The map's
// entries' order is preserved from the fact that Skylark dictionary entry order is stable
// (it's determined by insertion order).
for (Map.Entry<Label, ?> entry : selector.getEntries().entrySet()) {
Label condition = entry.getKey();
SelectorEntry.Builder selectorEntryBuilder =
SelectorEntry.newBuilder()
.setLabel(condition.toString())
.setIsDefaultValue(!selector.isValueSet(condition));
Object conditionValue = entry.getValue();
if (conditionValue != null) {
writeAttributeValueToBuilder(
new SelectorEntryBuilderAdapter(selectorEntryBuilder),
type,
conditionValue);
}
selectorBuilder.addEntries(selectorEntryBuilder);
}
selectorListBuilder.addElements(selectorBuilder);
}
attrPb.setSelectorList(selectorListBuilder);
}
/**
* Set the appropriate type and value. Since string and string list store values for multiple
* types, use the toString() method on the objects instead of casting them.
*/
@SuppressWarnings("unchecked")
private static void writeAttributeValueToBuilder(
AttributeValueBuilderAdapter builder, Type<?> type, Object value) {
if (type == INTEGER) {
builder.setIntValue((Integer) value);
} else if (type == STRING || type == LABEL || type == NODEP_LABEL || type == OUTPUT) {
builder.setStringValue(value.toString());
} else if (type == STRING_LIST || type == LABEL_LIST || type == NODEP_LABEL_LIST
|| type == OUTPUT_LIST || type == DISTRIBUTIONS) {
for (Object entry : (Collection<?>) value) {
builder.addStringListValue(entry.toString());
}
} else if (type == INTEGER_LIST) {
for (Integer entry : (Collection<Integer>) value) {
builder.addIntListValue(entry);
}
} else if (type == BOOLEAN) {
builder.setBooleanValue((Boolean) value);
} else if (type == TRISTATE) {
builder.setTristateValue(triStateToProto((TriState) value));
} else if (type == LICENSE) {
License license = (License) value;
Build.License.Builder licensePb = Build.License.newBuilder();
for (License.LicenseType licenseType : license.getLicenseTypes()) {
licensePb.addLicenseType(licenseType.toString());
}
for (Label exception : license.getExceptions()) {
licensePb.addException(exception.toString());
}
builder.setLicense(licensePb);
} else if (type == STRING_DICT) {
Map<String, String> dict = (Map<String, String>) value;
for (Map.Entry<String, String> keyValueList : dict.entrySet()) {
StringDictEntry.Builder entry =
StringDictEntry.newBuilder()
.setKey(keyValueList.getKey())
.setValue(keyValueList.getValue());
builder.addStringDictValue(entry);
}
} else if (type == STRING_LIST_DICT) {
Map<String, List<String>> dict = (Map<String, List<String>>) value;
for (Map.Entry<String, List<String>> dictEntry : dict.entrySet()) {
StringListDictEntry.Builder entry =
StringListDictEntry.newBuilder().setKey(dictEntry.getKey());
for (Object dictEntryValue : dictEntry.getValue()) {
entry.addValue(dictEntryValue.toString());
}
builder.addStringListDictValue(entry);
}
} else if (type == LABEL_DICT_UNARY) {
Map<String, Label> dict = (Map<String, Label>) value;
for (Map.Entry<String, Label> dictEntry : dict.entrySet()) {
LabelDictUnaryEntry.Builder entry =
LabelDictUnaryEntry.newBuilder()
.setKey(dictEntry.getKey())
.setValue(dictEntry.getValue().toString());
builder.addLabelDictUnaryValue(entry);
}
} else if (type == LABEL_KEYED_STRING_DICT) {
Map<Label, String> dict = (Map<Label, String>) value;
for (Map.Entry<Label, String> dictEntry : dict.entrySet()) {
LabelKeyedStringDictEntry.Builder entry =
LabelKeyedStringDictEntry.newBuilder()
.setKey(dictEntry.getKey().toString())
.setValue(dictEntry.getValue());
builder.addLabelKeyedStringDictValue(entry);
}
} else if (type == FILESET_ENTRY_LIST) {
List<FilesetEntry> filesetEntries = (List<FilesetEntry>) value;
for (FilesetEntry filesetEntry : filesetEntries) {
Build.FilesetEntry.Builder filesetEntryPb =
Build.FilesetEntry.newBuilder()
.setSource(filesetEntry.getSrcLabel().toString())
.setDestinationDirectory(filesetEntry.getDestDir().getPathString())
.setSymlinkBehavior(symlinkBehaviorToPb(filesetEntry.getSymlinkBehavior()))
.setStripPrefix(filesetEntry.getStripPrefix())
.setFilesPresent(filesetEntry.getFiles() != null);
if (filesetEntry.getFiles() != null) {
for (Label file : filesetEntry.getFiles()) {
filesetEntryPb.addFile(file.toString());
}
}
if (filesetEntry.getExcludes() != null) {
for (String exclude : filesetEntry.getExcludes()) {
filesetEntryPb.addExclude(exclude);
}
}
builder.addFilesetListValue(filesetEntryPb);
}
} else {
throw new AssertionError("Unknown type: " + type);
}
}
private static Tristate triStateToProto(TriState value) {
switch (value) {
case AUTO:
return Tristate.AUTO;
case NO:
return Tristate.NO;
case YES:
return Tristate.YES;
default:
throw new AssertionError("Expected AUTO/NO/YES to cover all possible cases");
}
}
// This is needed because I do not want to use the SymlinkBehavior from the
// protocol buffer all over the place, so there are two classes that do
// essentially the same thing.
private static Build.FilesetEntry.SymlinkBehavior symlinkBehaviorToPb(
FilesetEntry.SymlinkBehavior symlinkBehavior) {
switch (symlinkBehavior) {
case COPY:
return Build.FilesetEntry.SymlinkBehavior.COPY;
case DEREFERENCE:
return Build.FilesetEntry.SymlinkBehavior.DEREFERENCE;
default:
throw new AssertionError("Unhandled FilesetEntry.SymlinkBehavior");
}
}
/**
* An adapter used by {@link #writeAttributeValueToBuilder} in order to reuse the same code for
* writing to both {@link Build.Attribute.Builder} and {@link SelectorEntry.Builder} objects.
*/
private interface AttributeValueBuilderAdapter {
void addStringListValue(String s);
void addFilesetListValue(Build.FilesetEntry.Builder builder);
void addLabelDictUnaryValue(LabelDictUnaryEntry.Builder builder);
void addLabelKeyedStringDictValue(LabelKeyedStringDictEntry.Builder builder);
void addLabelListDictValue(LabelListDictEntry.Builder builder);
void addIntListValue(int i);
void addStringDictValue(StringDictEntry.Builder builder);
void addStringListDictValue(StringListDictEntry.Builder builder);
void setBooleanValue(boolean b);
void setIntValue(int i);
void setLicense(Build.License.Builder builder);
void setStringValue(String s);
void setTristateValue(Tristate tristate);
}
/**
* An {@link AttributeValueBuilderAdapter} which writes to a {@link Build.Attribute.Builder}.
*
* <p>If {@param encodeBooleanAndTriStateAsIntegerAndString} is {@code true}, then {@link
* Boolean} and {@link TriState} attribute values also write to the integer and string fields.
* This offers backwards compatibility to clients that expect attribute values of those types.
*/
private static class AttributeBuilderAdapter implements AttributeValueBuilderAdapter {
private final boolean encodeBooleanAndTriStateAsIntegerAndString;
private final Build.Attribute.Builder attributeBuilder;
private AttributeBuilderAdapter(
Build.Attribute.Builder attributeBuilder,
boolean encodeBooleanAndTriStateAsIntegerAndString) {
this.attributeBuilder = Preconditions.checkNotNull(attributeBuilder);
this.encodeBooleanAndTriStateAsIntegerAndString = encodeBooleanAndTriStateAsIntegerAndString;
}
@Override
public void addStringListValue(String s) {
attributeBuilder.addStringListValue(s);
}
@Override
public void addFilesetListValue(Build.FilesetEntry.Builder builder) {
attributeBuilder.addFilesetListValue(builder);
}
@Override
public void addLabelDictUnaryValue(LabelDictUnaryEntry.Builder builder) {
attributeBuilder.addLabelDictUnaryValue(builder);
}
@Override
public void addLabelKeyedStringDictValue(LabelKeyedStringDictEntry.Builder builder) {
attributeBuilder.addLabelKeyedStringDictValue(builder);
}
@Override
public void addLabelListDictValue(LabelListDictEntry.Builder builder) {
attributeBuilder.addLabelListDictValue(builder);
}
@Override
public void addIntListValue(int i) {
attributeBuilder.addIntListValue(i);
}
@Override
public void addStringDictValue(StringDictEntry.Builder builder) {
attributeBuilder.addStringDictValue(builder);
}
@Override
public void addStringListDictValue(StringListDictEntry.Builder builder) {
attributeBuilder.addStringListDictValue(builder);
}
@Override
public void setBooleanValue(boolean b) {
if (b) {
attributeBuilder.setBooleanValue(true);
if (encodeBooleanAndTriStateAsIntegerAndString) {
attributeBuilder.setStringValue("true");
attributeBuilder.setIntValue(1);
}
} else {
attributeBuilder.setBooleanValue(false);
if (encodeBooleanAndTriStateAsIntegerAndString) {
attributeBuilder.setStringValue("false");
attributeBuilder.setIntValue(0);
}
}
}
@Override
public void setIntValue(int i) {
attributeBuilder.setIntValue(i);
}
@Override
public void setLicense(Build.License.Builder builder) {
attributeBuilder.setLicense(builder);
}
@Override
public void setStringValue(String s) {
attributeBuilder.setStringValue(s);
}
@Override
public void setTristateValue(Tristate tristate) {
switch (tristate) {
case AUTO:
attributeBuilder.setTristateValue(Tristate.AUTO);
if (encodeBooleanAndTriStateAsIntegerAndString) {
attributeBuilder.setIntValue(-1);
attributeBuilder.setStringValue("auto");
}
break;
case NO:
attributeBuilder.setTristateValue(Tristate.NO);
if (encodeBooleanAndTriStateAsIntegerAndString) {
attributeBuilder.setIntValue(0);
attributeBuilder.setStringValue("no");
}
break;
case YES:
attributeBuilder.setTristateValue(Tristate.YES);
if (encodeBooleanAndTriStateAsIntegerAndString) {
attributeBuilder.setIntValue(1);
attributeBuilder.setStringValue("yes");
}
break;
default:
throw new AssertionError("Expected AUTO/NO/YES to cover all possible cases");
}
}
}
/**
* An {@link AttributeValueBuilderAdapter} which writes to a {@link SelectorEntry.Builder}.
*
* <p>Note that there is no {@code encodeBooleanAndTriStateAsIntegerAndString} parameter needed
* here. This is because the clients that expect those alternate encodings of boolean and
* tristate attribute values do not support {@link SelectorList} values. When providing output to
* those clients, we compute the set of possible attribute values (expanding {@link SelectorList}
* values, evaluating computed defaults, and flattening collections of collections; see {@link
* com.google.devtools.build.lib.packages.AggregatingAttributeMapper#getPossibleAttributeValues}
* and {@link
* com.google.devtools.build.lib.packages.AggregatingAttributeMapper#flattenAttributeValues}).
*/
private static class SelectorEntryBuilderAdapter implements AttributeValueBuilderAdapter {
private final SelectorEntry.Builder selectorEntryBuilder;
private SelectorEntryBuilderAdapter(SelectorEntry.Builder selectorEntryBuilder) {
this.selectorEntryBuilder = Preconditions.checkNotNull(selectorEntryBuilder);
}
@Override
public void addStringListValue(String s) {
selectorEntryBuilder.addStringListValue(s);
}
@Override
public void addFilesetListValue(Build.FilesetEntry.Builder builder) {
selectorEntryBuilder.addFilesetListValue(builder);
}
@Override
public void addLabelDictUnaryValue(LabelDictUnaryEntry.Builder builder) {
selectorEntryBuilder.addLabelDictUnaryValue(builder);
}
@Override
public void addLabelKeyedStringDictValue(LabelKeyedStringDictEntry.Builder builder) {
selectorEntryBuilder.addLabelKeyedStringDictValue(builder);
}
@Override
public void addLabelListDictValue(LabelListDictEntry.Builder builder) {
selectorEntryBuilder.addLabelListDictValue(builder);
}
@Override
public void addIntListValue(int i) {
selectorEntryBuilder.addIntListValue(i);
}
@Override
public void addStringDictValue(StringDictEntry.Builder builder) {
selectorEntryBuilder.addStringDictValue(builder);
}
@Override
public void addStringListDictValue(StringListDictEntry.Builder builder) {
selectorEntryBuilder.addStringListDictValue(builder);
}
@Override
public void setBooleanValue(boolean b) {
selectorEntryBuilder.setBooleanValue(b);
}
@Override
public void setIntValue(int i) {
selectorEntryBuilder.setIntValue(i);
}
@Override
public void setLicense(Build.License.Builder builder) {
selectorEntryBuilder.setLicense(builder);
}
@Override
public void setStringValue(String s) {
selectorEntryBuilder.setStringValue(s);
}
@Override
public void setTristateValue(Tristate tristate) {
selectorEntryBuilder.setTristateValue(tristate);
}
}
}