| // 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.syntax.Type.BOOLEAN; |
| import static com.google.devtools.build.lib.syntax.Type.INTEGER; |
| import static com.google.devtools.build.lib.syntax.Type.INTEGER_LIST; |
| import static com.google.devtools.build.lib.syntax.Type.STRING; |
| import static com.google.devtools.build.lib.syntax.Type.STRING_DICT; |
| import static com.google.devtools.build.lib.syntax.Type.STRING_LIST; |
| import static com.google.devtools.build.lib.syntax.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 com.google.devtools.build.lib.syntax.Type; |
| 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); |
| } |
| } |
| } |